@ethora/chat-component-rn 26.8.4 → 26.8.6

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 (417) hide show
  1. package/README.md +55 -0
  2. package/lib/commonjs/assets/emojiData.js +40 -0
  3. package/lib/commonjs/assets/emojiData.js.map +1 -0
  4. package/lib/commonjs/assets/icons.js +1 -1
  5. package/lib/commonjs/assets/icons.js.map +1 -1
  6. package/lib/commonjs/components/Header/HeaderRoomList.js +12 -7
  7. package/lib/commonjs/components/Header/HeaderRoomList.js.map +1 -1
  8. package/lib/commonjs/components/MainComponents/ChatHeader.js +7 -9
  9. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  10. package/lib/commonjs/components/MainComponents/ChatRoom.js +47 -26
  11. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  12. package/lib/commonjs/components/MainComponents/ChatWrapper.js +58 -21
  13. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  14. package/lib/commonjs/components/MainComponents/KeyboardInputDock.js +40 -0
  15. package/lib/commonjs/components/MainComponents/KeyboardInputDock.js.map +1 -0
  16. package/lib/commonjs/components/MainComponents/MessageContainer.js +4 -12
  17. package/lib/commonjs/components/MainComponents/MessageContainer.js.map +1 -1
  18. package/lib/commonjs/components/MainComponents/MessageList.js +110 -35
  19. package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
  20. package/lib/commonjs/components/MainComponents/RoomList.js +32 -29
  21. package/lib/commonjs/components/MainComponents/RoomList.js.map +1 -1
  22. package/lib/commonjs/components/MainComponents/RoomStack.js +270 -0
  23. package/lib/commonjs/components/MainComponents/RoomStack.js.map +1 -0
  24. package/lib/commonjs/components/MainComponents/SystemMessage.js +6 -2
  25. package/lib/commonjs/components/MainComponents/SystemMessage.js.map +1 -1
  26. package/lib/commonjs/components/MessageBubble/Avatar.js +5 -1
  27. package/lib/commonjs/components/MessageBubble/Avatar.js.map +1 -1
  28. package/lib/commonjs/components/MessageBubble/BottomReplyContainer.js +17 -21
  29. package/lib/commonjs/components/MessageBubble/BottomReplyContainer.js.map +1 -1
  30. package/lib/commonjs/components/MessageBubble/EmojiPickerSheet.js +447 -0
  31. package/lib/commonjs/components/MessageBubble/EmojiPickerSheet.js.map +1 -0
  32. package/lib/commonjs/components/MessageBubble/Message.js +200 -132
  33. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  34. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +213 -12
  35. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  36. package/lib/commonjs/components/MessageBubble/MessageReaction.js +113 -94
  37. package/lib/commonjs/components/MessageBubble/MessageReaction.js.map +1 -1
  38. package/lib/commonjs/components/MessageBubble/MessageReply.js +66 -24
  39. package/lib/commonjs/components/MessageBubble/MessageReply.js.map +1 -1
  40. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +6 -1
  41. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  42. package/lib/commonjs/components/Modals/Modal/Modal.js +24 -0
  43. package/lib/commonjs/components/Modals/Modal/Modal.js.map +1 -1
  44. package/lib/commonjs/components/Modals/Modal/SwipeBackLayer.js +178 -0
  45. package/lib/commonjs/components/Modals/Modal/SwipeBackLayer.js.map +1 -0
  46. package/lib/commonjs/components/Modals/ProfileHeader/ProfileHeader.js +28 -10
  47. package/lib/commonjs/components/Modals/ProfileHeader/ProfileHeader.js.map +1 -1
  48. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +9 -3
  49. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  50. package/lib/commonjs/components/RoomComponents/ChatRoomItem.js +5 -4
  51. package/lib/commonjs/components/RoomComponents/ChatRoomItem.js.map +1 -1
  52. package/lib/commonjs/components/RoomComponents/styled/LastMessageEmoji.js +8 -9
  53. package/lib/commonjs/components/RoomComponents/styled/LastMessageEmoji.js.map +1 -1
  54. package/lib/commonjs/components/Thread/ThreadWrapper.js +265 -152
  55. package/lib/commonjs/components/Thread/ThreadWrapper.js.map +1 -1
  56. package/lib/commonjs/components/styled/DateLabel.js +6 -3
  57. package/lib/commonjs/components/styled/DateLabel.js.map +1 -1
  58. package/lib/commonjs/context/MessageNotificationContext.js +188 -9
  59. package/lib/commonjs/context/MessageNotificationContext.js.map +1 -1
  60. package/lib/commonjs/context/xmppProvider.js +22 -7
  61. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  62. package/lib/commonjs/helpers/createRoomFromApi.js +36 -2
  63. package/lib/commonjs/helpers/createRoomFromApi.js.map +1 -1
  64. package/lib/commonjs/helpers/emoji.js +51 -0
  65. package/lib/commonjs/helpers/emoji.js.map +1 -0
  66. package/lib/commonjs/helpers/haptics.js +29 -0
  67. package/lib/commonjs/helpers/haptics.js.map +1 -0
  68. package/lib/commonjs/helpers/historyPreloadScheduler.js +32 -66
  69. package/lib/commonjs/helpers/historyPreloadScheduler.js.map +1 -1
  70. package/lib/commonjs/helpers/insertMessageWithDelimiter.js +21 -12
  71. package/lib/commonjs/helpers/insertMessageWithDelimiter.js.map +1 -1
  72. package/lib/commonjs/helpers/keyboardLayout.js +23 -1
  73. package/lib/commonjs/helpers/keyboardLayout.js.map +1 -1
  74. package/lib/commonjs/helpers/mamReactions.js +57 -0
  75. package/lib/commonjs/helpers/mamReactions.js.map +1 -0
  76. package/lib/commonjs/helpers/parseMessageReference.js +42 -0
  77. package/lib/commonjs/helpers/parseMessageReference.js.map +1 -0
  78. package/lib/commonjs/helpers/reactionsConfig.js +17 -0
  79. package/lib/commonjs/helpers/reactionsConfig.js.map +1 -0
  80. package/lib/commonjs/helpers/updateMessagesTillLast.js +17 -2
  81. package/lib/commonjs/helpers/updateMessagesTillLast.js.map +1 -1
  82. package/lib/commonjs/hooks/useHeapSender.js +3 -1
  83. package/lib/commonjs/hooks/useHeapSender.js.map +1 -1
  84. package/lib/commonjs/hooks/useMessageHeapState.js +3 -2
  85. package/lib/commonjs/hooks/useMessageHeapState.js.map +1 -1
  86. package/lib/commonjs/hooks/useRoomInitialization.js +16 -7
  87. package/lib/commonjs/hooks/useRoomInitialization.js.map +1 -1
  88. package/lib/commonjs/hooks/useSendMessage.js +14 -8
  89. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  90. package/lib/commonjs/hooks/useUnreadMessagesCounter.js +1 -1
  91. package/lib/commonjs/hooks/useUnreadMessagesCounter.js.map +1 -1
  92. package/lib/commonjs/networking/stanzaHandlers.js +106 -50
  93. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  94. package/lib/commonjs/networking/xmpp/getHistory.xmpp.js +29 -74
  95. package/lib/commonjs/networking/xmpp/getHistory.xmpp.js.map +1 -1
  96. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +4 -0
  97. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  98. package/lib/commonjs/networking/xmpp/mamRouter.js +186 -0
  99. package/lib/commonjs/networking/xmpp/mamRouter.js.map +1 -0
  100. package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js +41 -4
  101. package/lib/commonjs/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
  102. package/lib/commonjs/networking/xmppClient.js +98 -23
  103. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  104. package/lib/commonjs/roomStore/Middleware/newMessageMidlleware.js +29 -34
  105. package/lib/commonjs/roomStore/Middleware/newMessageMidlleware.js.map +1 -1
  106. package/lib/commonjs/roomStore/Middleware/perfMiddleware.js +51 -0
  107. package/lib/commonjs/roomStore/Middleware/perfMiddleware.js.map +1 -0
  108. package/lib/commonjs/roomStore/Middleware/reactionsMiddleware.js +66 -43
  109. package/lib/commonjs/roomStore/Middleware/reactionsMiddleware.js.map +1 -1
  110. package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js +88 -68
  111. package/lib/commonjs/roomStore/Middleware/unreadMidlleware.js.map +1 -1
  112. package/lib/commonjs/roomStore/index.js +18 -14
  113. package/lib/commonjs/roomStore/index.js.map +1 -1
  114. package/lib/commonjs/roomStore/persistBackend.js +96 -0
  115. package/lib/commonjs/roomStore/persistBackend.js.map +1 -0
  116. package/lib/commonjs/roomStore/persistence.js +245 -50
  117. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  118. package/lib/commonjs/roomStore/roomsSlice.js +282 -136
  119. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  120. package/lib/commonjs/theme/theme.js +3 -0
  121. package/lib/commonjs/theme/theme.js.map +1 -1
  122. package/lib/commonjs/utils/devLogger.js +14 -0
  123. package/lib/commonjs/utils/devLogger.js.map +1 -1
  124. package/lib/commonjs/version.js +1 -1
  125. package/lib/module/assets/emojiData.js +36 -0
  126. package/lib/module/assets/emojiData.js.map +1 -0
  127. package/lib/module/assets/icons.js +1 -1
  128. package/lib/module/assets/icons.js.map +1 -1
  129. package/lib/module/components/Header/HeaderRoomList.js +12 -7
  130. package/lib/module/components/Header/HeaderRoomList.js.map +1 -1
  131. package/lib/module/components/MainComponents/ChatHeader.js +8 -10
  132. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  133. package/lib/module/components/MainComponents/ChatRoom.js +49 -28
  134. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  135. package/lib/module/components/MainComponents/ChatWrapper.js +60 -23
  136. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  137. package/lib/module/components/MainComponents/KeyboardInputDock.js +33 -0
  138. package/lib/module/components/MainComponents/KeyboardInputDock.js.map +1 -0
  139. package/lib/module/components/MainComponents/MessageContainer.js +7 -14
  140. package/lib/module/components/MainComponents/MessageContainer.js.map +1 -1
  141. package/lib/module/components/MainComponents/MessageList.js +110 -35
  142. package/lib/module/components/MainComponents/MessageList.js.map +1 -1
  143. package/lib/module/components/MainComponents/RoomList.js +32 -29
  144. package/lib/module/components/MainComponents/RoomList.js.map +1 -1
  145. package/lib/module/components/MainComponents/RoomStack.js +266 -0
  146. package/lib/module/components/MainComponents/RoomStack.js.map +1 -0
  147. package/lib/module/components/MainComponents/SystemMessage.js +6 -2
  148. package/lib/module/components/MainComponents/SystemMessage.js.map +1 -1
  149. package/lib/module/components/MessageBubble/Avatar.js +5 -1
  150. package/lib/module/components/MessageBubble/Avatar.js.map +1 -1
  151. package/lib/module/components/MessageBubble/BottomReplyContainer.js +18 -22
  152. package/lib/module/components/MessageBubble/BottomReplyContainer.js.map +1 -1
  153. package/lib/module/components/MessageBubble/EmojiPickerSheet.js +438 -0
  154. package/lib/module/components/MessageBubble/EmojiPickerSheet.js.map +1 -0
  155. package/lib/module/components/MessageBubble/Message.js +203 -134
  156. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  157. package/lib/module/components/MessageBubble/MessageInteractions.js +215 -14
  158. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  159. package/lib/module/components/MessageBubble/MessageReaction.js +115 -94
  160. package/lib/module/components/MessageBubble/MessageReaction.js.map +1 -1
  161. package/lib/module/components/MessageBubble/MessageReply.js +65 -24
  162. package/lib/module/components/MessageBubble/MessageReply.js.map +1 -1
  163. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +6 -1
  164. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  165. package/lib/module/components/Modals/Modal/Modal.js +25 -1
  166. package/lib/module/components/Modals/Modal/Modal.js.map +1 -1
  167. package/lib/module/components/Modals/Modal/SwipeBackLayer.js +172 -0
  168. package/lib/module/components/Modals/Modal/SwipeBackLayer.js.map +1 -0
  169. package/lib/module/components/Modals/ProfileHeader/ProfileHeader.js +29 -11
  170. package/lib/module/components/Modals/ProfileHeader/ProfileHeader.js.map +1 -1
  171. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +10 -4
  172. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  173. package/lib/module/components/RoomComponents/ChatRoomItem.js +5 -4
  174. package/lib/module/components/RoomComponents/ChatRoomItem.js.map +1 -1
  175. package/lib/module/components/RoomComponents/styled/LastMessageEmoji.js +8 -9
  176. package/lib/module/components/RoomComponents/styled/LastMessageEmoji.js.map +1 -1
  177. package/lib/module/components/Thread/ThreadWrapper.js +264 -152
  178. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  179. package/lib/module/components/styled/DateLabel.js +6 -3
  180. package/lib/module/components/styled/DateLabel.js.map +1 -1
  181. package/lib/module/context/MessageNotificationContext.js +190 -11
  182. package/lib/module/context/MessageNotificationContext.js.map +1 -1
  183. package/lib/module/context/xmppProvider.js +22 -7
  184. package/lib/module/context/xmppProvider.js.map +1 -1
  185. package/lib/module/helpers/createRoomFromApi.js +36 -2
  186. package/lib/module/helpers/createRoomFromApi.js.map +1 -1
  187. package/lib/module/helpers/emoji.js +44 -0
  188. package/lib/module/helpers/emoji.js.map +1 -0
  189. package/lib/module/helpers/haptics.js +24 -0
  190. package/lib/module/helpers/haptics.js.map +1 -0
  191. package/lib/module/helpers/historyPreloadScheduler.js +32 -66
  192. package/lib/module/helpers/historyPreloadScheduler.js.map +1 -1
  193. package/lib/module/helpers/insertMessageWithDelimiter.js +21 -12
  194. package/lib/module/helpers/insertMessageWithDelimiter.js.map +1 -1
  195. package/lib/module/helpers/keyboardLayout.js +20 -0
  196. package/lib/module/helpers/keyboardLayout.js.map +1 -1
  197. package/lib/module/helpers/mamReactions.js +51 -0
  198. package/lib/module/helpers/mamReactions.js.map +1 -0
  199. package/lib/module/helpers/parseMessageReference.js +37 -0
  200. package/lib/module/helpers/parseMessageReference.js.map +1 -0
  201. package/lib/module/helpers/reactionsConfig.js +10 -0
  202. package/lib/module/helpers/reactionsConfig.js.map +1 -0
  203. package/lib/module/helpers/updateMessagesTillLast.js +17 -2
  204. package/lib/module/helpers/updateMessagesTillLast.js.map +1 -1
  205. package/lib/module/hooks/useHeapSender.js +3 -1
  206. package/lib/module/hooks/useHeapSender.js.map +1 -1
  207. package/lib/module/hooks/useMessageHeapState.js +3 -2
  208. package/lib/module/hooks/useMessageHeapState.js.map +1 -1
  209. package/lib/module/hooks/useRoomInitialization.js +17 -8
  210. package/lib/module/hooks/useRoomInitialization.js.map +1 -1
  211. package/lib/module/hooks/useSendMessage.js +14 -8
  212. package/lib/module/hooks/useSendMessage.js.map +1 -1
  213. package/lib/module/hooks/useUnreadMessagesCounter.js +1 -1
  214. package/lib/module/hooks/useUnreadMessagesCounter.js.map +1 -1
  215. package/lib/module/networking/stanzaHandlers.js +107 -51
  216. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  217. package/lib/module/networking/xmpp/getHistory.xmpp.js +29 -74
  218. package/lib/module/networking/xmpp/getHistory.xmpp.js.map +1 -1
  219. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +4 -0
  220. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  221. package/lib/module/networking/xmpp/mamRouter.js +175 -0
  222. package/lib/module/networking/xmpp/mamRouter.js.map +1 -0
  223. package/lib/module/networking/xmpp/presenceInRoom.xmpp.js +38 -2
  224. package/lib/module/networking/xmpp/presenceInRoom.xmpp.js.map +1 -1
  225. package/lib/module/networking/xmppClient.js +99 -24
  226. package/lib/module/networking/xmppClient.js.map +1 -1
  227. package/lib/module/roomStore/Middleware/newMessageMidlleware.js +29 -34
  228. package/lib/module/roomStore/Middleware/newMessageMidlleware.js.map +1 -1
  229. package/lib/module/roomStore/Middleware/perfMiddleware.js +45 -0
  230. package/lib/module/roomStore/Middleware/perfMiddleware.js.map +1 -0
  231. package/lib/module/roomStore/Middleware/reactionsMiddleware.js +66 -43
  232. package/lib/module/roomStore/Middleware/reactionsMiddleware.js.map +1 -1
  233. package/lib/module/roomStore/Middleware/unreadMidlleware.js +89 -69
  234. package/lib/module/roomStore/Middleware/unreadMidlleware.js.map +1 -1
  235. package/lib/module/roomStore/index.js +19 -15
  236. package/lib/module/roomStore/index.js.map +1 -1
  237. package/lib/module/roomStore/persistBackend.js +89 -0
  238. package/lib/module/roomStore/persistBackend.js.map +1 -0
  239. package/lib/module/roomStore/persistence.js +245 -48
  240. package/lib/module/roomStore/persistence.js.map +1 -1
  241. package/lib/module/roomStore/roomsSlice.js +275 -134
  242. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  243. package/lib/module/theme/theme.js +3 -0
  244. package/lib/module/theme/theme.js.map +1 -1
  245. package/lib/module/utils/devLogger.js +13 -0
  246. package/lib/module/utils/devLogger.js.map +1 -1
  247. package/lib/module/version.js +1 -1
  248. package/lib/typescript/assets/emojiData.d.ts +8 -0
  249. package/lib/typescript/assets/emojiData.d.ts.map +1 -0
  250. package/lib/typescript/components/Header/HeaderRoomList.d.ts.map +1 -1
  251. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  252. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  253. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  254. package/lib/typescript/components/MainComponents/KeyboardInputDock.d.ts +16 -0
  255. package/lib/typescript/components/MainComponents/KeyboardInputDock.d.ts.map +1 -0
  256. package/lib/typescript/components/MainComponents/MessageContainer.d.ts +2 -2
  257. package/lib/typescript/components/MainComponents/MessageContainer.d.ts.map +1 -1
  258. package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
  259. package/lib/typescript/components/MainComponents/RoomList.d.ts.map +1 -1
  260. package/lib/typescript/components/MainComponents/RoomStack.d.ts +42 -0
  261. package/lib/typescript/components/MainComponents/RoomStack.d.ts.map +1 -0
  262. package/lib/typescript/components/MainComponents/SystemMessage.d.ts.map +1 -1
  263. package/lib/typescript/components/MessageBubble/Avatar.d.ts +2 -0
  264. package/lib/typescript/components/MessageBubble/Avatar.d.ts.map +1 -1
  265. package/lib/typescript/components/MessageBubble/BottomReplyContainer.d.ts.map +1 -1
  266. package/lib/typescript/components/MessageBubble/EmojiPickerSheet.d.ts +11 -0
  267. package/lib/typescript/components/MessageBubble/EmojiPickerSheet.d.ts.map +1 -0
  268. package/lib/typescript/components/MessageBubble/Message.d.ts +2 -2
  269. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  270. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts +2 -0
  271. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  272. package/lib/typescript/components/MessageBubble/MessageReaction.d.ts +1 -0
  273. package/lib/typescript/components/MessageBubble/MessageReaction.d.ts.map +1 -1
  274. package/lib/typescript/components/MessageBubble/MessageReply.d.ts +7 -7
  275. package/lib/typescript/components/MessageBubble/MessageReply.d.ts.map +1 -1
  276. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  277. package/lib/typescript/components/Modals/Modal/Modal.d.ts.map +1 -1
  278. package/lib/typescript/components/Modals/Modal/SwipeBackLayer.d.ts +16 -0
  279. package/lib/typescript/components/Modals/Modal/SwipeBackLayer.d.ts.map +1 -0
  280. package/lib/typescript/components/Modals/ProfileHeader/ProfileHeader.d.ts.map +1 -1
  281. package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
  282. package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts +2 -2
  283. package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts.map +1 -1
  284. package/lib/typescript/components/RoomComponents/styled/LastMessageEmoji.d.ts +3 -6
  285. package/lib/typescript/components/RoomComponents/styled/LastMessageEmoji.d.ts.map +1 -1
  286. package/lib/typescript/components/Thread/ThreadWrapper.d.ts +7 -1
  287. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  288. package/lib/typescript/components/styled/DateLabel.d.ts +1 -0
  289. package/lib/typescript/components/styled/DateLabel.d.ts.map +1 -1
  290. package/lib/typescript/context/MessageNotificationContext.d.ts.map +1 -1
  291. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  292. package/lib/typescript/helpers/createRoomFromApi.d.ts.map +1 -1
  293. package/lib/typescript/helpers/emoji.d.ts +6 -0
  294. package/lib/typescript/helpers/emoji.d.ts.map +1 -0
  295. package/lib/typescript/helpers/haptics.d.ts +3 -0
  296. package/lib/typescript/helpers/haptics.d.ts.map +1 -0
  297. package/lib/typescript/helpers/historyPreloadScheduler.d.ts +1 -0
  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 +6 -0
  301. package/lib/typescript/helpers/keyboardLayout.d.ts.map +1 -1
  302. package/lib/typescript/helpers/mamReactions.d.ts +13 -0
  303. package/lib/typescript/helpers/mamReactions.d.ts.map +1 -0
  304. package/lib/typescript/helpers/parseMessageReference.d.ts +16 -0
  305. package/lib/typescript/helpers/parseMessageReference.d.ts.map +1 -0
  306. package/lib/typescript/helpers/reactionsConfig.d.ts +5 -0
  307. package/lib/typescript/helpers/reactionsConfig.d.ts.map +1 -0
  308. package/lib/typescript/helpers/updateMessagesTillLast.d.ts.map +1 -1
  309. package/lib/typescript/hooks/useHeapSender.d.ts.map +1 -1
  310. package/lib/typescript/hooks/useMessageHeapState.d.ts.map +1 -1
  311. package/lib/typescript/hooks/useRoomInitialization.d.ts +2 -2
  312. package/lib/typescript/hooks/useRoomInitialization.d.ts.map +1 -1
  313. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  314. package/lib/typescript/networking/stanzaHandlers.d.ts +4 -2
  315. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  316. package/lib/typescript/networking/xmpp/getHistory.xmpp.d.ts +3 -1
  317. package/lib/typescript/networking/xmpp/getHistory.xmpp.d.ts.map +1 -1
  318. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  319. package/lib/typescript/networking/xmpp/mamRouter.d.ts +23 -0
  320. package/lib/typescript/networking/xmpp/mamRouter.d.ts.map +1 -0
  321. package/lib/typescript/networking/xmpp/presenceInRoom.xmpp.d.ts +1 -0
  322. package/lib/typescript/networking/xmpp/presenceInRoom.xmpp.d.ts.map +1 -1
  323. package/lib/typescript/networking/xmppClient.d.ts +22 -1
  324. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  325. package/lib/typescript/roomStore/Middleware/newMessageMidlleware.d.ts.map +1 -1
  326. package/lib/typescript/roomStore/Middleware/perfMiddleware.d.ts +4 -0
  327. package/lib/typescript/roomStore/Middleware/perfMiddleware.d.ts.map +1 -0
  328. package/lib/typescript/roomStore/Middleware/reactionsMiddleware.d.ts +9 -0
  329. package/lib/typescript/roomStore/Middleware/reactionsMiddleware.d.ts.map +1 -1
  330. package/lib/typescript/roomStore/Middleware/unreadMidlleware.d.ts.map +1 -1
  331. package/lib/typescript/roomStore/index.d.ts.map +1 -1
  332. package/lib/typescript/roomStore/persistBackend.d.ts +11 -0
  333. package/lib/typescript/roomStore/persistBackend.d.ts.map +1 -0
  334. package/lib/typescript/roomStore/persistence.d.ts +5 -6
  335. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  336. package/lib/typescript/roomStore/roomsSlice.d.ts +41 -1
  337. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  338. package/lib/typescript/theme/theme.d.ts +4 -1
  339. package/lib/typescript/theme/theme.d.ts.map +1 -1
  340. package/lib/typescript/types/models/message.model.d.ts +4 -0
  341. package/lib/typescript/types/models/message.model.d.ts.map +1 -1
  342. package/lib/typescript/types/models/room.model.d.ts +16 -0
  343. package/lib/typescript/types/models/room.model.d.ts.map +1 -1
  344. package/lib/typescript/types/models/xmpp.model.d.ts +6 -1
  345. package/lib/typescript/types/models/xmpp.model.d.ts.map +1 -1
  346. package/lib/typescript/types/types.d.ts +15 -3
  347. package/lib/typescript/types/types.d.ts.map +1 -1
  348. package/lib/typescript/utils/devLogger.d.ts +8 -0
  349. package/lib/typescript/utils/devLogger.d.ts.map +1 -1
  350. package/lib/typescript/version.d.ts +1 -1
  351. package/package.json +10 -1
  352. package/src/assets/emojiData.ts +2146 -0
  353. package/src/assets/icons.tsx +5 -5
  354. package/src/components/Header/HeaderRoomList.tsx +7 -5
  355. package/src/components/MainComponents/ChatHeader.tsx +16 -7
  356. package/src/components/MainComponents/ChatRoom.tsx +64 -32
  357. package/src/components/MainComponents/ChatWrapper.tsx +73 -21
  358. package/src/components/MainComponents/KeyboardInputDock.tsx +47 -0
  359. package/src/components/MainComponents/MessageContainer.tsx +7 -15
  360. package/src/components/MainComponents/MessageList.tsx +96 -43
  361. package/src/components/MainComponents/RoomList.tsx +28 -29
  362. package/src/components/MainComponents/RoomStack.tsx +309 -0
  363. package/src/components/MainComponents/SystemMessage.tsx +6 -2
  364. package/src/components/MessageBubble/Avatar.tsx +7 -1
  365. package/src/components/MessageBubble/BottomReplyContainer.tsx +19 -21
  366. package/src/components/MessageBubble/EmojiPickerSheet.tsx +468 -0
  367. package/src/components/MessageBubble/Message.tsx +146 -87
  368. package/src/components/MessageBubble/MessageInteractions.tsx +213 -12
  369. package/src/components/MessageBubble/MessageReaction.tsx +120 -96
  370. package/src/components/MessageBubble/MessageReply.tsx +65 -32
  371. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +4 -0
  372. package/src/components/Modals/Modal/Modal.tsx +32 -1
  373. package/src/components/Modals/Modal/SwipeBackLayer.tsx +221 -0
  374. package/src/components/Modals/ProfileHeader/ProfileHeader.tsx +29 -8
  375. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +6 -3
  376. package/src/components/RoomComponents/ChatRoomItem.tsx +11 -4
  377. package/src/components/RoomComponents/styled/LastMessageEmoji.tsx +7 -8
  378. package/src/components/Thread/ThreadWrapper.tsx +274 -198
  379. package/src/components/styled/DateLabel.tsx +7 -3
  380. package/src/context/MessageNotificationContext.tsx +205 -7
  381. package/src/context/xmppProvider.tsx +29 -9
  382. package/src/helpers/createRoomFromApi.tsx +37 -1
  383. package/src/helpers/emoji.ts +39 -0
  384. package/src/helpers/haptics.ts +19 -0
  385. package/src/helpers/historyPreloadScheduler.ts +37 -69
  386. package/src/helpers/insertMessageWithDelimiter.ts +23 -16
  387. package/src/helpers/keyboardLayout.ts +26 -0
  388. package/src/helpers/mamReactions.ts +59 -0
  389. package/src/helpers/parseMessageReference.ts +38 -0
  390. package/src/helpers/reactionsConfig.ts +13 -0
  391. package/src/helpers/updateMessagesTillLast.tsx +14 -2
  392. package/src/hooks/useHeapSender.tsx +3 -2
  393. package/src/hooks/useMessageHeapState.tsx +6 -2
  394. package/src/hooks/useRoomInitialization.tsx +32 -15
  395. package/src/hooks/useSendMessage.tsx +18 -11
  396. package/src/hooks/useUnreadMessagesCounter.ts +1 -1
  397. package/src/networking/stanzaHandlers.ts +116 -60
  398. package/src/networking/xmpp/getHistory.xmpp.ts +44 -106
  399. package/src/networking/xmpp/handleStanzas.xmpp.ts +2 -0
  400. package/src/networking/xmpp/mamRouter.ts +176 -0
  401. package/src/networking/xmpp/presenceInRoom.xmpp.ts +46 -3
  402. package/src/networking/xmppClient.ts +105 -20
  403. package/src/roomStore/Middleware/newMessageMidlleware.tsx +35 -38
  404. package/src/roomStore/Middleware/perfMiddleware.ts +50 -0
  405. package/src/roomStore/Middleware/reactionsMiddleware.tsx +53 -52
  406. package/src/roomStore/Middleware/unreadMidlleware.tsx +92 -80
  407. package/src/roomStore/index.ts +28 -27
  408. package/src/roomStore/persistBackend.ts +96 -0
  409. package/src/roomStore/persistence.ts +280 -73
  410. package/src/roomStore/roomsSlice.ts +304 -155
  411. package/src/theme/theme.ts +10 -1
  412. package/src/types/models/message.model.ts +4 -0
  413. package/src/types/models/room.model.ts +17 -0
  414. package/src/types/models/xmpp.model.ts +4 -2
  415. package/src/types/types.ts +15 -3
  416. package/src/utils/devLogger.ts +16 -0
  417. package/src/version.ts +1 -1
@@ -15,6 +15,9 @@ import type XmppClient from '../networking/xmppClient';
15
15
  // page backward arbitrarily far without instantly losing what they
16
16
  // just fetched. After cap eviction we drop from the head (oldest).
17
17
  export const RUNTIME_MESSAGE_LIMIT = 100;
18
+ /** One preload page: "N+" badges only ever mean "at least this many". */
19
+ export const UNREAD_CAP_PAGE = 10;
20
+ export const RUNTIME_MESSAGE_CEILING = 2000;
18
21
 
19
22
  // Body strings the server uses for call signaling broadcasts (call-token,
20
23
  // call-state ringing/ended, etc). These should never reach the chat
@@ -107,11 +110,215 @@ const collapseCallLogDuplicates = (messages: IMessage[]): IMessage[] => {
107
110
  });
108
111
  };
109
112
 
110
- const enforceMessageCap = (messages: IMessage[]): void => {
113
+ type InsertOutcome = { grew: boolean; atHead: boolean };
114
+ const NO_GROWTH: InsertOutcome = { grew: false, atHead: false };
115
+
116
+ const insertRoomMessage = (
117
+ state: WritableDraft<RoomMessagesState>,
118
+ roomJID: string,
119
+ message: IMessage,
120
+ start?: boolean
121
+ ): InsertOutcome => {
122
+ if (isCallSignalMessage(message)) {
123
+ return NO_GROWTH;
124
+ }
125
+
126
+ if (!state.rooms[roomJID]) {
127
+ return NO_GROWTH;
128
+ }
129
+
130
+ if (!state.rooms[roomJID].messages) {
131
+ state.rooms[roomJID].messages = [];
132
+ }
133
+
134
+ const roomMessages = state.rooms[roomJID].messages!;
135
+
136
+ const incomingCallLog = message.callLog;
137
+ if (incomingCallLog?.callId) {
138
+ const existingCallIdx = roomMessages.findIndex(
139
+ (msg) => msg.callLog?.callId === incomingCallLog.callId
140
+ );
141
+ if (existingCallIdx !== -1) {
142
+ const existing = roomMessages[existingCallIdx];
143
+ const merged = mergeCallLogEntries(existing as IMessage, message);
144
+ if (merged !== (existing as IMessage)) {
145
+ roomMessages[existingCallIdx] = merged;
146
+ }
147
+ return NO_GROWTH;
148
+ }
149
+ }
150
+
151
+ takePendingReactions(state, roomJID, message);
152
+ const lengthBefore = roomMessages.length;
153
+ const tailBefore = roomMessages[lengthBefore - 1];
154
+
155
+ if (roomMessages.length === 0 || start) {
156
+ roomMessages.unshift(message);
157
+ } else {
158
+ const lastViewedValue = state.rooms[roomJID].lastViewedTimestamp;
159
+ const lastViewedTimestamp =
160
+ state.visibleRoomJID === roomJID
161
+ ? null
162
+ : lastViewedValue
163
+ ? lastViewedValue
164
+ : null;
165
+
166
+ insertMessageWithDelimiter(roomMessages, message, lastViewedTimestamp);
167
+ }
168
+
169
+ const grew = roomMessages.length > lengthBefore;
170
+ const atHead =
171
+ grew &&
172
+ lengthBefore > 0 &&
173
+ roomMessages[roomMessages.length - 1] === tailBefore;
174
+ return { grew, atHead };
175
+ };
176
+
177
+ const capAfterGrowth = (
178
+ messages: IMessage[],
179
+ lengthBefore: number,
180
+ atHead: boolean
181
+ ): void => {
182
+ if (atHead) {
183
+ enforceMessageCap(messages, RUNTIME_MESSAGE_CEILING);
184
+ return;
185
+ }
186
+ enforceMessageCap(
187
+ messages,
188
+ Math.min(RUNTIME_MESSAGE_CEILING, Math.max(RUNTIME_MESSAGE_LIMIT, lengthBefore))
189
+ );
190
+ };
191
+
192
+ const bumpLastMessageTimestamp = (
193
+ state: WritableDraft<RoomMessagesState>,
194
+ roomJID: string,
195
+ message: IMessage
196
+ ): void => {
197
+ const room = state.rooms[roomJID];
198
+ if (!room || message?.id === 'delimiter-new') {return;}
199
+ const ts = Number(message?.id);
200
+ if (!Number.isFinite(ts)) {return;}
201
+ if ((room.lastMessageTimestamp ?? 0) <= ts) {
202
+ room.lastMessageTimestamp = ts;
203
+ }
204
+ };
205
+
206
+ const upsertRoom = (
207
+ state: WritableDraft<RoomMessagesState>,
208
+ roomData: IRoom
209
+ ): void => {
210
+ const existing = state.rooms[roomData.jid];
211
+ let lastViewed: number;
212
+ const serverMarker = state.privateStoreMarkers?.[roomData.jid] || 0;
213
+
214
+ if (roomData.lastViewedTimestamp != null && roomData.lastViewedTimestamp !== 0) {
215
+ lastViewed = roomData.lastViewedTimestamp;
216
+ } else if (existing?.lastViewedTimestamp != null && existing.lastViewedTimestamp > 0) {
217
+ lastViewed = existing.lastViewedTimestamp;
218
+ } else if (serverMarker > 0) {
219
+ lastViewed = serverMarker;
220
+ } else {
221
+ const msgs = roomData.messages || [];
222
+ let newest = 0;
223
+ for (const m of msgs) {
224
+ const t = (m as any)?.messageTimestampMs ||
225
+ (m?.date ? new Date(m.date).getTime() : 0);
226
+ if (t > newest) {newest = t;}
227
+ }
228
+ lastViewed = newest; // 0 if no messages → cold-start safe
229
+ }
230
+
231
+ const incomingMessages = Array.isArray(roomData.messages)
232
+ ? roomData.messages
233
+ : [];
234
+ const existingMessages = Array.isArray(existing?.messages)
235
+ ? existing!.messages
236
+ : [];
237
+ state.rooms[roomData.jid] = {
238
+ ...roomData,
239
+ icon: roomData.icon !== undefined ? roomData.icon : existing?.icon,
240
+ roomBg: roomData.roomBg !== undefined ? roomData.roomBg : existing?.roomBg,
241
+ messages:
242
+ existingMessages.length > 0 ? existingMessages : incomingMessages,
243
+ lastViewedTimestamp: lastViewed,
244
+ unreadMessages:
245
+ roomData.unreadMessages ?? existing?.unreadMessages ?? 0,
246
+ unreadBaselineTimestamp:
247
+ existing?.unreadBaselineTimestamp ??
248
+ existing?.lastViewedTimestamp ??
249
+ (roomData as any).unreadBaselineTimestamp ??
250
+ 0,
251
+ };
252
+ };
253
+
254
+ /** Order two numeric stanza ids (microsecond timestamps, too long for a
255
+ * double) without BigInt: by length, then lexicographically. */
256
+ export const compareStanzaIds = (a: string, b: string): number =>
257
+ a.length !== b.length ? a.length - b.length : a < b ? -1 : a > b ? 1 : 0;
258
+
259
+ export const applyReactionToMessage = (
260
+ message: IMessage,
261
+ from: string | undefined,
262
+ reactions: string[],
263
+ data?: Record<string, string>,
264
+ ts?: string
265
+ ): void => {
266
+ const fromId = String(from || '').split('/')[0].split('@')[0];
267
+ if (!fromId) {return;}
268
+ const list = (reactions || []).filter((r) => !!r);
269
+ const current = { ...(message.reaction || {}) };
270
+ const prev = current[fromId];
271
+ // An older reaction (from a history page loaded after a newer one) never
272
+ // overwrites what is already known.
273
+ if (ts && prev?.ts && compareStanzaIds(ts, prev.ts) < 0) {return;}
274
+ if (list.length === 0) {
275
+ if (ts) {
276
+ // Keep a "removed" marker so an older reaction can't come back.
277
+ current[fromId] = { emoji: [], data: data || {}, ts };
278
+ } else {
279
+ delete current[fromId];
280
+ }
281
+ } else {
282
+ current[fromId] = { emoji: list, data: data || {}, ...(ts ? { ts } : {}) };
283
+ }
284
+ const anyShown = Object.values(current).some((r) => r?.emoji?.length);
285
+ message.reaction = anyShown || Object.keys(current).length ? current : undefined;
286
+ };
287
+
288
+ type PendingReaction = {
289
+ from: string;
290
+ reactions: string[];
291
+ data?: Record<string, string>;
292
+ ts?: string;
293
+ };
294
+
295
+ /** Reactions whose message is not loaded yet — applied when it arrives. */
296
+ const takePendingReactions = (
297
+ state: WritableDraft<RoomMessagesState>,
298
+ roomJID: string,
299
+ message: IMessage
300
+ ): void => {
301
+ const byMessage = state.pendingReactions?.[roomJID];
302
+ if (!byMessage) {return;}
303
+ const keys = [String(message.id), message.xmppId ? String(message.xmppId) : ''];
304
+ for (const key of keys) {
305
+ const list = key ? byMessage[key] : undefined;
306
+ if (!list) {continue;}
307
+ for (const r of list) {
308
+ applyReactionToMessage(message, r.from, r.reactions, r.data, r.ts);
309
+ }
310
+ delete byMessage[key];
311
+ }
312
+ };
313
+
314
+ const enforceMessageCap = (
315
+ messages: IMessage[],
316
+ limit: number = RUNTIME_MESSAGE_LIMIT
317
+ ): void => {
111
318
  // Trim oldest until we're at/under the limit. Mutates in place
112
319
  // (immer-compatible inside reducers).
113
- while (messages.length > RUNTIME_MESSAGE_LIMIT) {
114
- messages.shift();
320
+ if (messages.length > limit) {
321
+ messages.splice(0, messages.length - limit);
115
322
  }
116
323
  };
117
324
 
@@ -202,10 +409,16 @@ function mergeHistoryIntoCache(
202
409
  // body). Preserve `isEdited` from the cached copy so a message edited
203
410
  // before reload keeps its marker instead of losing it on history sync.
204
411
  const prev = byId.get(String(m.id));
205
- byId.set(
206
- String(m.id),
207
- prev?.isEdited && !m.isEdited ? { ...m, isEdited: true } : m
208
- );
412
+ let next = prev?.isEdited && !m.isEdited ? { ...m, isEdited: true } : m;
413
+
414
+ if (prev?.reaction && !next.reaction) {
415
+ next = { ...next, reaction: prev.reaction };
416
+ }
417
+ // A history merge must not close a thread the user has open.
418
+ if ((prev as any)?.activeMessage && !(next as any).activeMessage) {
419
+ next = { ...next, activeMessage: true } as IMessage;
420
+ }
421
+ byId.set(String(m.id), next);
209
422
  }
210
423
  const merged = collapseCallLogDuplicates(
211
424
  Array.from(byId.values()).sort(byMs)
@@ -237,6 +450,9 @@ export interface RoomMessagesState {
237
450
  // — see `applyPrivateStoreMarkers`. Re-fetched every init/reconnect,
238
451
  // so it is intentionally NOT persisted.
239
452
  privateStoreMarkers: Record<string, number>;
453
+ /** `{ roomJID: { messageId: reactions[] } }` — reactions that arrived
454
+ * before their message (a newer history page), applied on insert. */
455
+ pendingReactions?: Record<string, Record<string, PendingReaction[]>>;
240
456
  // The single source of truth for "read up to here, but the user
241
457
  // hasn't reached the bottom yet" (`{ roomJID: boundaryMs }`).
242
458
  // Set by MessageList (via ChatRoom's `onReadBoundaryChange`) the
@@ -281,6 +497,7 @@ const initialState: RoomMessagesState = {
281
497
  pendingNotificationJid: null,
282
498
  privateStoreMarkers: {},
283
499
  readBoundaries: {},
500
+ pendingReactions: {},
284
501
  };
285
502
 
286
503
  const isValidRoomJid = (jid: unknown): jid is string => {
@@ -306,81 +523,26 @@ export const addRoomViaApi = createAsyncThunk(
306
523
  // emitted .d.ts (TS4023). See chatSettingsSlice.ts for the same pattern.
307
524
  const reducers = {
308
525
  addRoom(state: WritableDraft<RoomMessagesState>, action: PayloadAction<{ roomData: IRoom }>) {
309
- const { roomData } = action.payload;
310
- const existing = state.rooms[roomData.jid];
311
- // Default-marker resolution (cold-start unread bug):
312
- // 1. Explicit value on the payload wins.
313
- // 2. Existing redux value wins (preserves persisted/hydrated
314
- // markers when the privateStore pull lands before
315
- // /chats/my).
316
- // 3. Otherwise, anchor to the *newest known message* in the
317
- // payload — this marks everything currently in the room
318
- // as "seen" but lets any NEWER incoming message count as
319
- // unread. Previously this fell back to `Date.now()`, which
320
- // stamped a future-leaning marker that hid genuinely-new
321
- // messages received while the app was closed.
322
- // 4. If there are no messages at all yet, stamp `0` (=
323
- // "unknown — let the privateStore hydration set the real
324
- // marker before any future message arrives").
325
- let lastViewed: number;
326
- // Treat an incoming `0` as "unset". stanzaHandlers' addRoom passes
327
- // `lastViewedTimestamp: 0` as a placeholder; the old `!= null` check
328
- // let that 0 win and OVERWROTE the persisted/hydrated marker from
329
- // the previous session, so cold-start showed no unread badge for
330
- // messages received while the app was closed (bug #19/#20). A real
331
- // (non-zero) explicit value still wins; otherwise keep the existing
332
- // value; otherwise fall back to the server-side read marker fetched
333
- // from the private store (so a room the user has NEVER opened this
334
- // session still gets a real baseline — without it the unread
335
- // middleware's `lastViewedTimestamp > 0` gate skipped the room
336
- // forever and the badge never lit up); otherwise anchor to the
337
- // newest message in the payload.
338
- const serverMarker = state.privateStoreMarkers?.[roomData.jid] || 0;
339
- if (roomData.lastViewedTimestamp != null && roomData.lastViewedTimestamp !== 0) {
340
- lastViewed = roomData.lastViewedTimestamp;
341
- } else if (existing?.lastViewedTimestamp != null && existing.lastViewedTimestamp > 0) {
342
- lastViewed = existing.lastViewedTimestamp;
343
- } else if (serverMarker > 0) {
344
- lastViewed = serverMarker;
345
- } else {
346
- const msgs = roomData.messages || [];
347
- let newest = 0;
348
- for (const m of msgs) {
349
- const t = (m as any)?.messageTimestampMs ||
350
- (m?.date ? new Date(m.date).getTime() : 0);
351
- if (t > newest) {newest = t;}
352
- }
353
- lastViewed = newest; // 0 if no messages → cold-start safe
526
+ upsertRoom(state, action.payload.roomData);
527
+ },
528
+
529
+ setUnreadCounts(state: WritableDraft<RoomMessagesState>, action: PayloadAction<Record<string, number>>) {
530
+ for (const [jid, count] of Object.entries(action.payload || {})) {
531
+ const room = state.rooms[jid];
532
+ if (!room) {continue;}
533
+ if (room.unreadMessages !== count) {room.unreadMessages = count;}
534
+ // "N+" only means "a whole page was unread"; an exact count below a
535
+ // page (or read to zero) settles it.
536
+ if (room.unreadCapped && count < UNREAD_CAP_PAGE) {room.unreadCapped = false;}
537
+ }
538
+ },
539
+ /** Several rooms in one action — the cold-start rehydrate of the whole
540
+ * cache used to be one `addRoom` per room, each running every
541
+ * middleware over every room already added (O(rooms²)). */
542
+ addRooms(state: WritableDraft<RoomMessagesState>, action: PayloadAction<{ rooms: IRoom[] }>) {
543
+ for (const roomData of action.payload.rooms || []) {
544
+ if (roomData?.jid) {upsertRoom(state, roomData);}
354
545
  }
355
- // Preserve cached messages + unread when the incoming room carries
356
- // none. The /chats/my fetch (rooms.api) builds rooms with
357
- // `messages: []` and no unread; without this guard, re-entering the
358
- // app replaced every cached room with an empty one — wiping history
359
- // AND the unread badge before the history scheduler could re-merge
360
- // (the user's "it clears cache, unread and messages" bug). New rooms
361
- // (no existing) fall through to the incoming values. Mirrors
362
- // addRoomFromApi's preservation.
363
- const incomingMessages = Array.isArray(roomData.messages)
364
- ? roomData.messages
365
- : [];
366
- const existingMessages = Array.isArray(existing?.messages)
367
- ? existing!.messages
368
- : [];
369
- state.rooms[roomData.jid] = {
370
- ...roomData,
371
- icon: roomData.icon !== undefined ? roomData.icon : existing?.icon,
372
- roomBg: roomData.roomBg !== undefined ? roomData.roomBg : existing?.roomBg,
373
- messages:
374
- existingMessages.length > 0 ? existingMessages : incomingMessages,
375
- lastViewedTimestamp: lastViewed,
376
- unreadMessages:
377
- roomData.unreadMessages ?? existing?.unreadMessages ?? 0,
378
- unreadBaselineTimestamp:
379
- existing?.unreadBaselineTimestamp ??
380
- existing?.lastViewedTimestamp ??
381
- (roomData as any).unreadBaselineTimestamp ??
382
- 0,
383
- };
384
546
  },
385
547
  deleteRoom(state: WritableDraft<RoomMessagesState>, action: PayloadAction<{ jid: string }>) {
386
548
  const { jid } = action.payload;
@@ -441,6 +603,7 @@ const reducers = {
441
603
  room.messages.map((message) => {
442
604
  if (message.id === messageId) {
443
605
  message.isDeleted = true;
606
+ message.reaction = undefined;
444
607
  }
445
608
  });
446
609
  },
@@ -486,79 +649,51 @@ const reducers = {
486
649
  }>
487
650
  ) {
488
651
  const { roomJID, message, start } = action.payload;
489
-
490
- // Call signaling broadcasts ("call-token", "call-state", etc.)
491
- // sometimes slip past the live XMPP filter (MAM history, mucsub
492
- // catch-up), drop them here so they never land in the transcript
493
- // or the room-list "last message" preview.
494
- if (isCallSignalMessage(message)) {
495
- return;
652
+ const lengthBefore = state.rooms[roomJID]?.messages?.length || 0;
653
+ const outcome = insertRoomMessage(state, roomJID, message, start);
654
+ if (outcome.grew) {
655
+ capAfterGrowth(state.rooms[roomJID].messages!, lengthBefore, outcome.atHead);
496
656
  }
497
-
498
- // Guard against the "stanza arrived before /chats/my completed"
499
- // race — without this, the optional-chain `?.messages` falls
500
- // back to undefined, and the assignment below tries to set
501
- // `.messages` on undefined → TypeError. Caller can safely
502
- // ignore the message; the room will get its messages array on
503
- // `addRoom` when the API response lands.
504
- if (!state.rooms[roomJID]) {
657
+ bumpLastMessageTimestamp(state, roomJID, message);
658
+ },
659
+ /**
660
+ * A whole page of messages (a MAM history page) in ONE action: the same
661
+ * per-message insert as `addRoomMessage`, but the cap, the middleware
662
+ * chain and every subscriber run once per page instead of once per
663
+ * message.
664
+ */
665
+ addRoomMessages(
666
+ state: WritableDraft<RoomMessagesState>,
667
+ action: PayloadAction<{
668
+ roomJID: string;
669
+ messages: IMessage[];
670
+ start?: boolean;
671
+ }>
672
+ ) {
673
+ const { roomJID, messages, start } = action.payload;
674
+ if (!state.rooms[roomJID] || !Array.isArray(messages)) {
505
675
  return;
506
676
  }
507
-
508
- if (!state.rooms[roomJID].messages) {
509
- state.rooms[roomJID].messages = [];
510
- }
511
-
512
- const roomMessages = state.rooms[roomJID].messages;
513
-
514
- // Collapse multiple call-state events for the same call into a single
515
- // log entry. Sources: the client-side fallback written at hangup (id
516
- // "calllog-<callId>", exists only locally) and the server broadcast(s),
517
- // which can fire once per participant leaving (earlier ones carry a
518
- // partial durationMs). Rules:
519
- // - the SERVER copy is canonical for identity (id/date/xmppId): its
520
- // archive id is what MAM and the catch-up anchor return later, so
521
- // keeping a local "calllog-" id around breaks anchor matching and
522
- // duplicates the entry on the next history merge;
523
- // - the LARGEST duration wins for display, so a 2-minute call doesn't
524
- // render as "2 sec".
525
- const incomingCallLog = message.callLog;
526
- if (incomingCallLog?.callId) {
527
- const existingCallIdx = roomMessages.findIndex(
528
- (msg) => msg.callLog?.callId === incomingCallLog.callId
529
- );
530
- if (existingCallIdx !== -1) {
531
- const existing = roomMessages[existingCallIdx];
532
- const merged = mergeCallLogEntries(existing as IMessage, message);
533
- if (merged !== (existing as IMessage)) {
534
- roomMessages[existingCallIdx] = merged;
535
- }
536
- return;
677
+ const lengthBefore = state.rooms[roomJID].messages?.length || 0;
678
+ let grew = false;
679
+ let anyAtTail = false;
680
+ let newest: IMessage | undefined;
681
+ for (const message of messages) {
682
+ const outcome = insertRoomMessage(state, roomJID, message, start);
683
+ if (outcome.grew) {
684
+ grew = true;
685
+ if (!outcome.atHead) {anyAtTail = true;}
686
+ }
687
+ if (!newest || Number(message?.id) > Number(newest.id)) {
688
+ newest = message;
537
689
  }
538
690
  }
539
-
540
- const lengthBefore = roomMessages.length;
541
-
542
- if (roomMessages.length === 0 || start) {
543
- roomMessages.unshift(message);
544
- } else {
545
- const lastViewedValue = state.rooms[roomJID].lastViewedTimestamp;
546
- const lastViewedTimestamp =
547
- state.visibleRoomJID === roomJID
548
- ? null
549
- : lastViewedValue
550
- ? lastViewedValue
551
- : null;
552
-
553
- insertMessageWithDelimiter(roomMessages, message, lastViewedTimestamp);
691
+ if (grew) {
692
+ // A page that only prepended older history is never trimmed.
693
+ capAfterGrowth(state.rooms[roomJID].messages!, lengthBefore, !anyAtTail);
554
694
  }
555
-
556
- // Apply the in-memory cap only when the array actually GREW
557
- // (i.e. this wasn't a dedupe/merge that left length unchanged).
558
- // Otherwise repeated echoes of the same message would chip away
559
- // at the oldest history for no reason.
560
- if (roomMessages.length > lengthBefore) {
561
- enforceMessageCap(roomMessages);
695
+ if (newest) {
696
+ bumpLastMessageTimestamp(state, roomJID, newest);
562
697
  }
563
698
  },
564
699
  deleteAllRooms(state: WritableDraft<RoomMessagesState>) {
@@ -798,18 +933,28 @@ const reducers = {
798
933
  data?: Record<string, string>;
799
934
  }>
800
935
  ) => {
801
- const { roomJID, messageId, reactions } = action.payload;
936
+ const { roomJID, messageId, reactions, from, data, latestReactionTimestamp } =
937
+ action.payload;
938
+ if (!from) {return;}
802
939
  const room = state.rooms[roomJID];
803
- if (!room?.messages) {return;}
804
- for (const msg of room.messages) {
805
- if (msg?.id === messageId) {
806
- (msg as any).reactions = reactions;
807
- break;
808
- }
940
+ const target = room?.messages?.find(
941
+ (msg) => msg?.id === messageId || msg?.xmppId === messageId
942
+ );
943
+ if (target) {
944
+ applyReactionToMessage(target as IMessage, from, reactions, data, latestReactionTimestamp);
945
+ return;
946
+ }
947
+ // The message is not loaded (yet): keep the reaction for it.
948
+ if (!state.pendingReactions) {state.pendingReactions = {};}
949
+ const byMessage = (state.pendingReactions[roomJID] ||= {});
950
+ const list = (byMessage[messageId] ||= []);
951
+ if (list.length < 50) {
952
+ list.push({ from, reactions, data, ts: latestReactionTimestamp });
809
953
  }
810
954
  },
811
955
  setLogoutState: (state: WritableDraft<RoomMessagesState>) => {
812
956
  state.rooms = {};
957
+ state.pendingReactions = {};
813
958
  state.activeRoomJID = null;
814
959
  state.visibleRoomJID = null;
815
960
  state.isLoading = false;
@@ -911,6 +1056,7 @@ const reducers = {
911
1056
  room.historyComplete = patch.historyComplete;
912
1057
  }
913
1058
  if (Array.isArray(patch.messages)) {
1059
+ for (const m of patch.messages) {takePendingReactions(state, patch.jid, m);}
914
1060
  // Merge the fetched page into cache by message id rather than
915
1061
  // replacing — preserves older history + unread on re-entry. Only
916
1062
  // a true gap (no overlapping id) clears this chat's cache. See
@@ -1043,6 +1189,9 @@ export const {
1043
1189
  deleteRoom,
1044
1190
  updateRoom,
1045
1191
  applyRoomsPreloadBatch,
1192
+ addRoomMessages,
1193
+ addRooms,
1194
+ setUnreadCounts,
1046
1195
  setPendingNotificationJid,
1047
1196
  clearPendingNotificationJid,
1048
1197
  setReactions,
@@ -33,8 +33,10 @@ export interface ChatThemeColors {
33
33
  icon: string;
34
34
  /** Sender name above incoming bubbles. */
35
35
  senderName: string;
36
- /** Day-separator pill text; its background is a tint of it. */
36
+ /** Day-separator pill text (also system-message pills). */
37
37
  dateLabel: string;
38
+ /** Day-separator / system-message pill background. */
39
+ dateLabelBackground: string;
38
40
 
39
41
  /** Ground of the room list screen. */
40
42
  listBackground: string;
@@ -100,6 +102,7 @@ export const LIGHT_THEME: ChatThemeColors = {
100
102
  icon: DEFAULT_PRIMARY,
101
103
  senderName: DEFAULT_PRIMARY,
102
104
  dateLabel: DEFAULT_PRIMARY,
105
+ dateLabelBackground: '#E7EDF9',
103
106
 
104
107
  listBackground: '#E8EDF2',
105
108
  chatBackground: '#F3F6FC',
@@ -133,6 +136,7 @@ export const DARK_THEME: ChatThemeColors = {
133
136
  icon: '#4C8DFF',
134
137
  senderName: '#7FB0FF',
135
138
  dateLabel: '#7FB0FF',
139
+ dateLabelBackground: '#26303D',
136
140
 
137
141
  listBackground: '#0F1216',
138
142
  chatBackground: '#141A21',
@@ -174,6 +178,7 @@ export interface ThemeConfigInput {
174
178
  icon?: string;
175
179
  senderName?: string;
176
180
  dateLabel?: string;
181
+ dateLabelBackground?: string;
177
182
  };
178
183
  messageColor?: {
179
184
  backgroundMessage?: string;
@@ -239,6 +244,10 @@ export const resolveTheme = (
239
244
  icon: pick(c?.icon, c?.primary, LIGHT_THEME.icon)!,
240
245
  senderName: pick(c?.senderName, c?.primary, LIGHT_THEME.senderName)!,
241
246
  dateLabel: pick(c?.dateLabel, c?.primary, LIGHT_THEME.dateLabel)!,
247
+ dateLabelBackground: pick(
248
+ c?.dateLabelBackground,
249
+ LIGHT_THEME.dateLabelBackground
250
+ )!,
242
251
  chatBackground: pick(config?.backgroundChat?.color, LIGHT_THEME.chatBackground)!,
243
252
  messageBackground: pick(
244
253
  config?.messageColor?.backgroundMessage,
@@ -50,6 +50,10 @@ export interface IMessage {
50
50
  export interface ReactionMessage {
51
51
  emoji: string[];
52
52
  data: Record<string, string> | undefined;
53
+ /** Server stanza id of the reaction (microsecond timestamp). History
54
+ * pages arrive newest-first, so an older reaction must never overwrite
55
+ * a newer one; an empty `emoji` with a `ts` is a "removed" marker. */
56
+ ts?: string;
53
57
  }
54
58
 
55
59
  export interface IReply extends IMessage {}
@@ -60,6 +60,21 @@ export interface IRoom {
60
60
  historyComplete?: boolean;
61
61
  }
62
62
 
63
+ /** The flattened last-message doc `GET /v1/chats/my` embeds per room.
64
+ * Only present when the backend supports it; every field is optional. */
65
+ export interface ApiRoomLastMessage {
66
+ body?: string;
67
+ truncated?: boolean;
68
+ from?: string;
69
+ fromUserId?: string;
70
+ messageId?: string;
71
+ stanzaId?: string;
72
+ createdAt?: string;
73
+ isOwn?: boolean;
74
+ senderFirstName?: string;
75
+ senderLastName?: string;
76
+ }
77
+
63
78
  export interface ApiRoom {
64
79
  name: string;
65
80
  type: 'public' | 'group' | 'private';
@@ -80,6 +95,8 @@ export interface ApiRoom {
80
95
  participants?: number;
81
96
  icon?: string;
82
97
  muted?: boolean;
98
+ /** See ApiRoomLastMessage. */
99
+ lastMessage?: ApiRoomLastMessage;
83
100
  }
84
101
 
85
102
  export interface PostRoom {
@@ -32,6 +32,8 @@ export interface HistoryFetchOptions {
32
32
  coalesceRoom?: boolean;
33
33
  skipIfPreloaded?: boolean;
34
34
  source?: HistorySource;
35
+ /** The caller merges the page into the store itself. */
36
+ selfApplied?: boolean;
35
37
  }
36
38
 
37
39
  export interface XmppClientInterface {
@@ -133,11 +135,11 @@ export interface XmppClientInterface {
133
135
  chats?: string[]
134
136
  ): Promise<void>;
135
137
  sendMediaMessageStanza(roomJID: string, data: any, id?: string): void;
136
- sendMessageReactionStanza?(
138
+ sendMessageReactionStanza(
137
139
  messageId: string,
138
140
  roomJid: string,
139
141
  reactionsList: string[],
140
- reactionSymbol?: string
142
+ data?: { firstName?: string; lastName?: string }
141
143
  ): void;
142
144
  sendTextMessageWithTranslateTagStanza?(
143
145
  roomJID: string,