@ethora/chat-component-rn 26.10.5 → 26.10.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 (365) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +15 -0
  3. package/app.plugin.js +2 -0
  4. package/lib/commonjs/assets/icons.js +2 -2
  5. package/lib/commonjs/assets/icons.js.map +1 -1
  6. package/lib/commonjs/components/MainComponents/AlbumGrid.js +152 -0
  7. package/lib/commonjs/components/MainComponents/AlbumGrid.js.map +1 -0
  8. package/lib/commonjs/components/MainComponents/ChatRoom.js +2 -2
  9. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  10. package/lib/commonjs/components/MainComponents/MediaMessage.js +199 -123
  11. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  12. package/lib/commonjs/components/MainComponents/MediaUploadOverlay.js +109 -0
  13. package/lib/commonjs/components/MainComponents/MediaUploadOverlay.js.map +1 -0
  14. package/lib/commonjs/components/MessageBubble/MediaCaption.js +51 -0
  15. package/lib/commonjs/components/MessageBubble/MediaCaption.js.map +1 -0
  16. package/lib/commonjs/components/MessageBubble/Message.js +40 -1
  17. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  18. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +2 -1
  19. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  20. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
  21. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  22. package/lib/commonjs/components/RoomComponents/LastMessageItem.js +20 -4
  23. package/lib/commonjs/components/RoomComponents/LastMessageItem.js.map +1 -1
  24. package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js +3 -2
  25. package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  26. package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js +3 -2
  27. package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  28. package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
  29. package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  30. package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js +3 -2
  31. package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  32. package/lib/commonjs/components/Thread/ThreadWrapper.js +3 -2
  33. package/lib/commonjs/components/Thread/ThreadWrapper.js.map +1 -1
  34. package/lib/commonjs/components/styled/MediaFilePreview.js +60 -47
  35. package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
  36. package/lib/commonjs/components/styled/MessageImage.js +16 -6
  37. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  38. package/lib/commonjs/components/styled/SendInput.js +99 -68
  39. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  40. package/lib/commonjs/components/styled/VideoMessage.js +24 -10
  41. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  42. package/lib/commonjs/context/xmppProvider.js +46 -0
  43. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  44. package/lib/commonjs/e2ee/fileEnvelope.js +6 -0
  45. package/lib/commonjs/e2ee/fileEnvelope.js.map +1 -1
  46. package/lib/commonjs/e2ee/sealedBody.js +32 -2
  47. package/lib/commonjs/e2ee/sealedBody.js.map +1 -1
  48. package/lib/commonjs/helpers/albumLayout.js +150 -0
  49. package/lib/commonjs/helpers/albumLayout.js.map +1 -0
  50. package/lib/commonjs/helpers/attachments.js +255 -0
  51. package/lib/commonjs/helpers/attachments.js.map +1 -0
  52. package/lib/commonjs/helpers/caption.js +214 -0
  53. package/lib/commonjs/helpers/caption.js.map +1 -0
  54. package/lib/commonjs/helpers/createMainMessageForThread.js +5 -1
  55. package/lib/commonjs/helpers/createMainMessageForThread.js.map +1 -1
  56. package/lib/commonjs/helpers/createMessageFromXml.js +19 -5
  57. package/lib/commonjs/helpers/createMessageFromXml.js.map +1 -1
  58. package/lib/commonjs/helpers/fileKind.js +40 -0
  59. package/lib/commonjs/helpers/fileKind.js.map +1 -0
  60. package/lib/commonjs/helpers/getDataFromXml.js +6 -1
  61. package/lib/commonjs/helpers/getDataFromXml.js.map +1 -1
  62. package/lib/commonjs/helpers/mapWithConcurrency.js +41 -0
  63. package/lib/commonjs/helpers/mapWithConcurrency.js.map +1 -0
  64. package/lib/commonjs/helpers/mediaLimits.js +33 -0
  65. package/lib/commonjs/helpers/mediaLimits.js.map +1 -0
  66. package/lib/commonjs/helpers/mediaSendRetry.js +64 -0
  67. package/lib/commonjs/helpers/mediaSendRetry.js.map +1 -0
  68. package/lib/commonjs/helpers/mediaWireData.js +62 -0
  69. package/lib/commonjs/helpers/mediaWireData.js.map +1 -0
  70. package/lib/commonjs/helpers/mentionSpans.js +19 -0
  71. package/lib/commonjs/helpers/mentionSpans.js.map +1 -0
  72. package/lib/commonjs/helpers/mimeToExtension.js +45 -0
  73. package/lib/commonjs/helpers/mimeToExtension.js.map +1 -1
  74. package/lib/commonjs/helpers/readReceipts.js +173 -0
  75. package/lib/commonjs/helpers/readReceipts.js.map +1 -0
  76. package/lib/commonjs/helpers/repairFilenameEncoding.js +79 -0
  77. package/lib/commonjs/helpers/repairFilenameEncoding.js.map +1 -0
  78. package/lib/commonjs/helpers/safeJson.js +18 -0
  79. package/lib/commonjs/helpers/safeJson.js.map +1 -0
  80. package/lib/commonjs/hooks/useSendMessage.js +384 -193
  81. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  82. package/lib/commonjs/i18n/strings.js +96 -0
  83. package/lib/commonjs/i18n/strings.js.map +1 -1
  84. package/lib/commonjs/networking/api-requests/auth.api.js +62 -3
  85. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  86. package/lib/commonjs/networking/stanzaHandlers.js +100 -3
  87. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  88. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +4 -2
  89. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  90. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js +28 -0
  91. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  92. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  93. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  94. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js +28 -0
  95. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  96. package/lib/commonjs/networking/xmppClient.js +64 -0
  97. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  98. package/lib/commonjs/roomStore/persistence.js +78 -5
  99. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  100. package/lib/commonjs/roomStore/roomHeapSlice.js +4 -0
  101. package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
  102. package/lib/commonjs/roomStore/roomsSlice.js +114 -1
  103. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  104. package/lib/commonjs/types/models/attachment.model.js +2 -0
  105. package/lib/commonjs/types/models/attachment.model.js.map +1 -0
  106. package/lib/commonjs/version.js +1 -1
  107. package/lib/module/assets/icons.js +1 -1
  108. package/lib/module/assets/icons.js.map +1 -1
  109. package/lib/module/components/MainComponents/AlbumGrid.js +146 -0
  110. package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -0
  111. package/lib/module/components/MainComponents/ChatRoom.js +2 -2
  112. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  113. package/lib/module/components/MainComponents/MediaMessage.js +200 -124
  114. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  115. package/lib/module/components/MainComponents/MediaUploadOverlay.js +104 -0
  116. package/lib/module/components/MainComponents/MediaUploadOverlay.js.map +1 -0
  117. package/lib/module/components/MessageBubble/MediaCaption.js +46 -0
  118. package/lib/module/components/MessageBubble/MediaCaption.js.map +1 -0
  119. package/lib/module/components/MessageBubble/Message.js +44 -5
  120. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  121. package/lib/module/components/MessageBubble/MessageInteractions.js +2 -1
  122. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  123. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
  124. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  125. package/lib/module/components/RoomComponents/LastMessageItem.js +20 -4
  126. package/lib/module/components/RoomComponents/LastMessageItem.js.map +1 -1
  127. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js +3 -2
  128. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  129. package/lib/module/components/RoomComponents/styled/LastMessageFile.js +3 -2
  130. package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  131. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
  132. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  133. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +3 -2
  134. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  135. package/lib/module/components/Thread/ThreadWrapper.js +3 -2
  136. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  137. package/lib/module/components/styled/MediaFilePreview.js +61 -48
  138. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  139. package/lib/module/components/styled/MessageImage.js +16 -6
  140. package/lib/module/components/styled/MessageImage.js.map +1 -1
  141. package/lib/module/components/styled/SendInput.js +99 -68
  142. package/lib/module/components/styled/SendInput.js.map +1 -1
  143. package/lib/module/components/styled/VideoMessage.js +24 -10
  144. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  145. package/lib/module/context/xmppProvider.js +46 -0
  146. package/lib/module/context/xmppProvider.js.map +1 -1
  147. package/lib/module/e2ee/fileEnvelope.js +1 -1
  148. package/lib/module/e2ee/fileEnvelope.js.map +1 -1
  149. package/lib/module/e2ee/sealedBody.js +32 -2
  150. package/lib/module/e2ee/sealedBody.js.map +1 -1
  151. package/lib/module/helpers/albumLayout.js +143 -0
  152. package/lib/module/helpers/albumLayout.js.map +1 -0
  153. package/lib/module/helpers/attachments.js +245 -0
  154. package/lib/module/helpers/attachments.js.map +1 -0
  155. package/lib/module/helpers/caption.js +207 -0
  156. package/lib/module/helpers/caption.js.map +1 -0
  157. package/lib/module/helpers/createMainMessageForThread.js +5 -1
  158. package/lib/module/helpers/createMainMessageForThread.js.map +1 -1
  159. package/lib/module/helpers/createMessageFromXml.js +20 -6
  160. package/lib/module/helpers/createMessageFromXml.js.map +1 -1
  161. package/lib/module/helpers/fileKind.js +34 -0
  162. package/lib/module/helpers/fileKind.js.map +1 -0
  163. package/lib/module/helpers/getDataFromXml.js +6 -1
  164. package/lib/module/helpers/getDataFromXml.js.map +1 -1
  165. package/lib/module/helpers/mapWithConcurrency.js +37 -0
  166. package/lib/module/helpers/mapWithConcurrency.js.map +1 -0
  167. package/lib/module/helpers/mediaLimits.js +27 -0
  168. package/lib/module/helpers/mediaLimits.js.map +1 -0
  169. package/lib/module/helpers/mediaSendRetry.js +55 -0
  170. package/lib/module/helpers/mediaSendRetry.js.map +1 -0
  171. package/lib/module/helpers/mediaWireData.js +58 -0
  172. package/lib/module/helpers/mediaWireData.js.map +1 -0
  173. package/lib/module/helpers/mentionSpans.js +14 -0
  174. package/lib/module/helpers/mentionSpans.js.map +1 -0
  175. package/lib/module/helpers/mimeToExtension.js +43 -0
  176. package/lib/module/helpers/mimeToExtension.js.map +1 -1
  177. package/lib/module/helpers/readReceipts.js +160 -0
  178. package/lib/module/helpers/readReceipts.js.map +1 -0
  179. package/lib/module/helpers/repairFilenameEncoding.js +74 -0
  180. package/lib/module/helpers/repairFilenameEncoding.js.map +1 -0
  181. package/lib/module/helpers/safeJson.js +13 -0
  182. package/lib/module/helpers/safeJson.js.map +1 -0
  183. package/lib/module/hooks/useSendMessage.js +387 -195
  184. package/lib/module/hooks/useSendMessage.js.map +1 -1
  185. package/lib/module/i18n/strings.js +96 -0
  186. package/lib/module/i18n/strings.js.map +1 -1
  187. package/lib/module/networking/api-requests/auth.api.js +62 -3
  188. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  189. package/lib/module/networking/stanzaHandlers.js +97 -4
  190. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  191. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +5 -3
  192. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  193. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js +25 -0
  194. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  195. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  196. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  197. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js +25 -0
  198. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  199. package/lib/module/networking/xmppClient.js +64 -0
  200. package/lib/module/networking/xmppClient.js.map +1 -1
  201. package/lib/module/roomStore/persistence.js +78 -5
  202. package/lib/module/roomStore/persistence.js.map +1 -1
  203. package/lib/module/roomStore/roomHeapSlice.js +4 -0
  204. package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
  205. package/lib/module/roomStore/roomsSlice.js +109 -0
  206. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  207. package/lib/module/types/models/attachment.model.js +2 -0
  208. package/lib/module/types/models/attachment.model.js.map +1 -0
  209. package/lib/module/version.js +1 -1
  210. package/lib/typescript/assets/icons.d.ts +6 -1
  211. package/lib/typescript/assets/icons.d.ts.map +1 -1
  212. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts +21 -0
  213. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -0
  214. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  215. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  216. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts +17 -0
  217. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts.map +1 -0
  218. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts +12 -0
  219. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts.map +1 -0
  220. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  221. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  222. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  223. package/lib/typescript/components/RoomComponents/LastMessageItem.d.ts.map +1 -1
  224. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts +2 -0
  225. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts.map +1 -1
  226. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts +2 -0
  227. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
  228. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts +2 -0
  229. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
  230. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts +2 -0
  231. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
  232. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  233. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  234. package/lib/typescript/components/styled/MessageImage.d.ts +10 -0
  235. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  236. package/lib/typescript/components/styled/SendInput.d.ts +7 -1
  237. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  238. package/lib/typescript/components/styled/VideoMessage.d.ts +7 -0
  239. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  240. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  241. package/lib/typescript/e2ee/fileEnvelope.d.ts +1 -1
  242. package/lib/typescript/e2ee/fileEnvelope.d.ts.map +1 -1
  243. package/lib/typescript/e2ee/sealedBody.d.ts +16 -0
  244. package/lib/typescript/e2ee/sealedBody.d.ts.map +1 -1
  245. package/lib/typescript/helpers/albumLayout.d.ts +66 -0
  246. package/lib/typescript/helpers/albumLayout.d.ts.map +1 -0
  247. package/lib/typescript/helpers/attachments.d.ts +55 -0
  248. package/lib/typescript/helpers/attachments.d.ts.map +1 -0
  249. package/lib/typescript/helpers/caption.d.ts +64 -0
  250. package/lib/typescript/helpers/caption.d.ts.map +1 -0
  251. package/lib/typescript/helpers/createMainMessageForThread.d.ts.map +1 -1
  252. package/lib/typescript/helpers/createMessageFromXml.d.ts.map +1 -1
  253. package/lib/typescript/helpers/fileKind.d.ts +20 -0
  254. package/lib/typescript/helpers/fileKind.d.ts.map +1 -0
  255. package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
  256. package/lib/typescript/helpers/mapWithConcurrency.d.ts +9 -0
  257. package/lib/typescript/helpers/mapWithConcurrency.d.ts.map +1 -0
  258. package/lib/typescript/helpers/mediaLimits.d.ts +13 -0
  259. package/lib/typescript/helpers/mediaLimits.d.ts.map +1 -0
  260. package/lib/typescript/helpers/mediaSendRetry.d.ts +26 -0
  261. package/lib/typescript/helpers/mediaSendRetry.d.ts.map +1 -0
  262. package/lib/typescript/helpers/mediaWireData.d.ts +16 -0
  263. package/lib/typescript/helpers/mediaWireData.d.ts.map +1 -0
  264. package/lib/typescript/helpers/mentionSpans.d.ts +8 -0
  265. package/lib/typescript/helpers/mentionSpans.d.ts.map +1 -0
  266. package/lib/typescript/helpers/mimeToExtension.d.ts +7 -0
  267. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
  268. package/lib/typescript/helpers/readReceipts.d.ts +82 -0
  269. package/lib/typescript/helpers/readReceipts.d.ts.map +1 -0
  270. package/lib/typescript/helpers/repairFilenameEncoding.d.ts +12 -0
  271. package/lib/typescript/helpers/repairFilenameEncoding.d.ts.map +1 -0
  272. package/lib/typescript/helpers/safeJson.d.ts +2 -0
  273. package/lib/typescript/helpers/safeJson.d.ts.map +1 -0
  274. package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
  275. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  276. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  277. package/lib/typescript/networking/api-requests/auth.api.d.ts +2 -0
  278. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  279. package/lib/typescript/networking/stanzaHandlers.d.ts +7 -3
  280. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  281. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  282. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts +3 -0
  283. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts.map +1 -0
  284. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts +12 -0
  285. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts.map +1 -1
  286. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts +3 -0
  287. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts.map +1 -0
  288. package/lib/typescript/networking/xmppClient.d.ts +15 -0
  289. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  290. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  291. package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
  292. package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
  293. package/lib/typescript/roomStore/roomsSlice.d.ts +67 -3
  294. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  295. package/lib/typescript/types/models/attachment.model.d.ts +52 -0
  296. package/lib/typescript/types/models/attachment.model.d.ts.map +1 -0
  297. package/lib/typescript/types/models/message.model.d.ts +26 -0
  298. package/lib/typescript/types/models/message.model.d.ts.map +1 -1
  299. package/lib/typescript/types/models/modal.model.d.ts +7 -0
  300. package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
  301. package/lib/typescript/types/models/room.model.d.ts +2 -0
  302. package/lib/typescript/types/models/room.model.d.ts.map +1 -1
  303. package/lib/typescript/types/types.d.ts +29 -1
  304. package/lib/typescript/types/types.d.ts.map +1 -1
  305. package/lib/typescript/version.d.ts +1 -1
  306. package/package.json +4 -1
  307. package/plugins/notification-service/Info.plist +31 -0
  308. package/plugins/notification-service/NotificationService.swift +82 -0
  309. package/plugins/withNotificationServiceExtension.js +137 -0
  310. package/src/assets/icons.tsx +20 -2
  311. package/src/components/MainComponents/AlbumGrid.tsx +183 -0
  312. package/src/components/MainComponents/ChatRoom.tsx +4 -2
  313. package/src/components/MainComponents/MediaMessage.tsx +246 -130
  314. package/src/components/MainComponents/MediaUploadOverlay.tsx +102 -0
  315. package/src/components/MessageBubble/MediaCaption.tsx +45 -0
  316. package/src/components/MessageBubble/Message.tsx +71 -3
  317. package/src/components/MessageBubble/MessageInteractions.tsx +2 -1
  318. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +64 -0
  319. package/src/components/RoomComponents/LastMessageItem.tsx +15 -4
  320. package/src/components/RoomComponents/styled/LastAudioMessage.tsx +6 -3
  321. package/src/components/RoomComponents/styled/LastMessageFile.tsx +6 -2
  322. package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +6 -2
  323. package/src/components/RoomComponents/styled/LastMessageVideo.tsx +6 -2
  324. package/src/components/Thread/ThreadWrapper.tsx +5 -9
  325. package/src/components/styled/MediaFilePreview.tsx +49 -34
  326. package/src/components/styled/MessageImage.tsx +21 -5
  327. package/src/components/styled/SendInput.tsx +101 -87
  328. package/src/components/styled/VideoMessage.tsx +34 -9
  329. package/src/context/xmppProvider.tsx +32 -0
  330. package/src/e2ee/fileEnvelope.ts +1 -1
  331. package/src/e2ee/sealedBody.ts +44 -5
  332. package/src/helpers/albumLayout.ts +207 -0
  333. package/src/helpers/attachments.ts +316 -0
  334. package/src/helpers/caption.ts +283 -0
  335. package/src/helpers/createMainMessageForThread.ts +6 -1
  336. package/src/helpers/createMessageFromXml.ts +21 -5
  337. package/src/helpers/fileKind.ts +38 -0
  338. package/src/helpers/getDataFromXml.ts +5 -1
  339. package/src/helpers/mapWithConcurrency.ts +32 -0
  340. package/src/helpers/mediaLimits.ts +27 -0
  341. package/src/helpers/mediaSendRetry.ts +59 -0
  342. package/src/helpers/mediaWireData.ts +57 -0
  343. package/src/helpers/mentionSpans.ts +17 -0
  344. package/src/helpers/mimeToExtension.ts +51 -0
  345. package/src/helpers/readReceipts.ts +219 -0
  346. package/src/helpers/repairFilenameEncoding.ts +82 -0
  347. package/src/helpers/safeJson.ts +11 -0
  348. package/src/hooks/useSendMessage.tsx +448 -249
  349. package/src/i18n/strings.ts +96 -0
  350. package/src/networking/api-requests/auth.api.ts +60 -3
  351. package/src/networking/stanzaHandlers.ts +91 -2
  352. package/src/networking/xmpp/handleStanzas.xmpp.ts +6 -2
  353. package/src/networking/xmpp/sendDisplayedMarker.xmpp.ts +31 -0
  354. package/src/networking/xmpp/sendMediaMessage.xmpp.ts +60 -3
  355. package/src/networking/xmpp/sendReceivedReceipt.xmpp.ts +31 -0
  356. package/src/networking/xmppClient.ts +48 -0
  357. package/src/roomStore/persistence.ts +94 -3
  358. package/src/roomStore/roomHeapSlice.ts +23 -1
  359. package/src/roomStore/roomsSlice.ts +112 -1
  360. package/src/types/models/attachment.model.ts +51 -0
  361. package/src/types/models/message.model.ts +29 -0
  362. package/src/types/models/modal.model.ts +7 -0
  363. package/src/types/models/room.model.ts +2 -0
  364. package/src/types/types.ts +35 -1
  365. package/src/version.ts +1 -1
@@ -14,6 +14,17 @@
14
14
  export type UiStringTable = Record<string, string>;
15
15
 
16
16
  const en: UiStringTable = {
17
+ 'attachment.moreCount': '+{count}',
18
+ 'attachment.moreLabel': '{count} more files',
19
+ 'attachment.previous': 'Previous file',
20
+ 'attachment.next': 'Next file',
21
+ 'attachment.position': '{index} of {total}',
22
+ 'attachment.count': '{count} attachments',
23
+ 'attachment.limit': 'You can attach up to {count} files to one message',
24
+ 'attachment.tooLarge': '"{name}" is over the {max} limit',
25
+ 'attachment.uploadFailed': 'Upload failed',
26
+ 'attachment.retryUpload': 'Retry',
27
+ 'attachment.remove': 'Remove file',
17
28
  'search.placeholder': 'Search...',
18
29
  'roomList.title': 'Chats',
19
30
  'connection.connecting': 'Connecting…',
@@ -37,6 +48,11 @@ const en: UiStringTable = {
37
48
  'date.yesterday': 'Yesterday',
38
49
  'message.deleted': 'Message deleted',
39
50
  'message.edited': 'edited',
51
+ 'message.receipt.sent': 'Sent',
52
+ 'message.receipt.delivered': 'Delivered',
53
+ 'message.receipt.read': 'Read',
54
+ 'message.receipt.readBy': 'Read by {names}',
55
+ 'message.receipt.deliveredTo': 'Delivered to {names}',
40
56
  'message.sending': 'sending...',
41
57
  'message.failedRetry': '! Failed — tap to retry',
42
58
  'thread.title': 'Thread',
@@ -403,6 +419,17 @@ const en: UiStringTable = {
403
419
  };
404
420
 
405
421
  const fr: UiStringTable = {
422
+ 'attachment.moreCount': '+{count}',
423
+ 'attachment.moreLabel': '{count} fichiers de plus',
424
+ 'attachment.previous': 'Fichier précédent',
425
+ 'attachment.next': 'Fichier suivant',
426
+ 'attachment.position': '{index} sur {total}',
427
+ 'attachment.count': '{count} pièces jointes',
428
+ 'attachment.limit': 'Un message peut contenir au plus {count} fichiers',
429
+ 'attachment.tooLarge': '« {name} » dépasse la limite de {max}',
430
+ 'attachment.uploadFailed': 'Échec du téléversement',
431
+ 'attachment.retryUpload': 'Réessayer',
432
+ 'attachment.remove': 'Retirer le fichier',
406
433
  'search.placeholder': 'Rechercher...',
407
434
  'roomList.title': 'Discussions',
408
435
  'connection.connecting': 'Connexion…',
@@ -426,6 +453,11 @@ const fr: UiStringTable = {
426
453
  'date.yesterday': 'Hier',
427
454
  'message.deleted': 'Message supprimé',
428
455
  'message.edited': 'modifié',
456
+ 'message.receipt.sent': 'Envoyé',
457
+ 'message.receipt.delivered': 'Distribué',
458
+ 'message.receipt.read': 'Lu',
459
+ 'message.receipt.readBy': 'Lu par {names}',
460
+ 'message.receipt.deliveredTo': 'Distribué à {names}',
429
461
  'message.sending': 'envoi...',
430
462
  'message.failedRetry': '! Échec — touchez pour réessayer',
431
463
  'thread.title': 'Fil',
@@ -792,6 +824,17 @@ const fr: UiStringTable = {
792
824
  };
793
825
 
794
826
  const es: UiStringTable = {
827
+ 'attachment.moreCount': '+{count}',
828
+ 'attachment.moreLabel': '{count} archivos más',
829
+ 'attachment.previous': 'Archivo anterior',
830
+ 'attachment.next': 'Archivo siguiente',
831
+ 'attachment.position': '{index} de {total}',
832
+ 'attachment.count': '{count} archivos adjuntos',
833
+ 'attachment.limit': 'Puedes adjuntar hasta {count} archivos en un mensaje',
834
+ 'attachment.tooLarge': '"{name}" supera el límite de {max}',
835
+ 'attachment.uploadFailed': 'Error al subir',
836
+ 'attachment.retryUpload': 'Reintentar',
837
+ 'attachment.remove': 'Quitar archivo',
795
838
  'search.placeholder': 'Buscar...',
796
839
  'roomList.title': 'Conversaciones',
797
840
  'connection.connecting': 'Conectando…',
@@ -815,6 +858,11 @@ const es: UiStringTable = {
815
858
  'date.yesterday': 'Ayer',
816
859
  'message.deleted': 'Mensaje eliminado',
817
860
  'message.edited': 'editado',
861
+ 'message.receipt.sent': 'Enviado',
862
+ 'message.receipt.delivered': 'Entregado',
863
+ 'message.receipt.read': 'Leído',
864
+ 'message.receipt.readBy': 'Leído por {names}',
865
+ 'message.receipt.deliveredTo': 'Entregado a {names}',
818
866
  'message.sending': 'enviando...',
819
867
  'message.failedRetry': '! Error — toca para reintentar',
820
868
  'thread.title': 'Hilo',
@@ -1181,6 +1229,17 @@ const es: UiStringTable = {
1181
1229
  };
1182
1230
 
1183
1231
  const pt: UiStringTable = {
1232
+ 'attachment.moreCount': '+{count}',
1233
+ 'attachment.moreLabel': 'mais {count} arquivos',
1234
+ 'attachment.previous': 'Arquivo anterior',
1235
+ 'attachment.next': 'Próximo arquivo',
1236
+ 'attachment.position': '{index} de {total}',
1237
+ 'attachment.count': '{count} anexos',
1238
+ 'attachment.limit': 'Pode anexar até {count} ficheiros a uma mensagem',
1239
+ 'attachment.tooLarge': '"{name}" excede o limite de {max}',
1240
+ 'attachment.uploadFailed': 'Falha no carregamento',
1241
+ 'attachment.retryUpload': 'Tentar novamente',
1242
+ 'attachment.remove': 'Remover ficheiro',
1184
1243
  'search.placeholder': 'Pesquisar...',
1185
1244
  'roomList.title': 'Conversas',
1186
1245
  'connection.connecting': 'A ligar…',
@@ -1204,6 +1263,11 @@ const pt: UiStringTable = {
1204
1263
  'date.yesterday': 'Ontem',
1205
1264
  'message.deleted': 'Mensagem eliminada',
1206
1265
  'message.edited': 'editado',
1266
+ 'message.receipt.sent': 'Enviada',
1267
+ 'message.receipt.delivered': 'Entregue',
1268
+ 'message.receipt.read': 'Lida',
1269
+ 'message.receipt.readBy': 'Lida por {names}',
1270
+ 'message.receipt.deliveredTo': 'Entregue para {names}',
1207
1271
  'message.sending': 'a enviar...',
1208
1272
  'message.failedRetry': '! Falhou — toque para tentar de novo',
1209
1273
  'thread.title': 'Tópico',
@@ -1570,6 +1634,17 @@ const pt: UiStringTable = {
1570
1634
  };
1571
1635
 
1572
1636
  const ht: UiStringTable = {
1637
+ 'attachment.moreCount': '+{count}',
1638
+ 'attachment.moreLabel': '{count} lòt fichye ankò',
1639
+ 'attachment.previous': 'Fichye anvan an',
1640
+ 'attachment.next': 'Fichye ki vini an',
1641
+ 'attachment.position': '{index} sou {total}',
1642
+ 'attachment.count': '{count} pyès jwenn',
1643
+ 'attachment.limit': 'Ou ka mete jiska {count} fichye nan yon mesaj',
1644
+ 'attachment.tooLarge': '"{name}" depase limit {max} la',
1645
+ 'attachment.uploadFailed': 'Telechajman an echwe',
1646
+ 'attachment.retryUpload': 'Eseye ankò',
1647
+ 'attachment.remove': 'Retire fichye a',
1573
1648
  'search.placeholder': 'Chèche...',
1574
1649
  'roomList.title': 'Chat yo',
1575
1650
  'connection.connecting': 'Ap konekte…',
@@ -1593,6 +1668,11 @@ const ht: UiStringTable = {
1593
1668
  'date.yesterday': 'Yè',
1594
1669
  'message.deleted': 'Mesaj efase',
1595
1670
  'message.edited': 'modifye',
1671
+ 'message.receipt.sent': 'Voye',
1672
+ 'message.receipt.delivered': 'Livre',
1673
+ 'message.receipt.read': 'Li',
1674
+ 'message.receipt.readBy': 'Li pa {names}',
1675
+ 'message.receipt.deliveredTo': 'Livre a {names}',
1596
1676
  'message.sending': 'ap voye...',
1597
1677
  'message.failedRetry': '! Echwe — tape pou eseye ankò',
1598
1678
  'thread.title': 'Fil',
@@ -1959,6 +2039,17 @@ const ht: UiStringTable = {
1959
2039
  };
1960
2040
 
1961
2041
  const zh: UiStringTable = {
2042
+ 'attachment.moreCount': '+{count}',
2043
+ 'attachment.moreLabel': '另外 {count} 个文件',
2044
+ 'attachment.previous': '上一个文件',
2045
+ 'attachment.next': '下一个文件',
2046
+ 'attachment.position': '第 {index} 个,共 {total} 个',
2047
+ 'attachment.count': '{count} 个附件',
2048
+ 'attachment.limit': '一条消息最多附带 {count} 个文件',
2049
+ 'attachment.tooLarge': '"{name}" 超过 {max} 的限制',
2050
+ 'attachment.uploadFailed': '上传失败',
2051
+ 'attachment.retryUpload': '重试',
2052
+ 'attachment.remove': '移除文件',
1962
2053
  'search.placeholder': '搜索...',
1963
2054
  'roomList.title': '聊天',
1964
2055
  'connection.connecting': '连接中…',
@@ -1982,6 +2073,11 @@ const zh: UiStringTable = {
1982
2073
  'date.yesterday': '昨天',
1983
2074
  'message.deleted': '消息已删除',
1984
2075
  'message.edited': '已编辑',
2076
+ 'message.receipt.sent': '已发送',
2077
+ 'message.receipt.delivered': '已送达',
2078
+ 'message.receipt.read': '已读',
2079
+ 'message.receipt.readBy': '已读:{names}',
2080
+ 'message.receipt.deliveredTo': '已送达:{names}',
1985
2081
  'message.sending': '发送中...',
1986
2082
  'message.failedRetry': '! 发送失败 — 点击重试',
1987
2083
  'thread.title': '话题',
@@ -189,10 +189,14 @@ const cloneFormData = (formData: FormData): FormData => {
189
189
  return copy;
190
190
  };
191
191
 
192
+ // How long an upload may sit without progress before it is aborted.
193
+ const UPLOAD_STALL_MS = 90_000;
194
+
192
195
  function postMultipart(
193
196
  url: string,
194
197
  body: FormData,
195
- path: string
198
+ path: string,
199
+ onProgress?: (percent: number) => void
196
200
  ): Promise<{ data: any }> {
197
201
  const token = store.getState().chatSettingStore?.user?.token ?? '';
198
202
 
@@ -249,10 +253,59 @@ function postMultipart(
249
253
  xhr.ontimeout = () => failNetwork('Upload timed out');
250
254
  xhr.onabort = () => failNetwork('Upload aborted');
251
255
 
256
+ // A stalled connection must not hold an upload (and the message waiting on
257
+ // it) forever: abort when the body makes no progress for UPLOAD_STALL_MS,
258
+ // and again while the server takes that long to answer once it is up.
259
+ let stallTimer: ReturnType<typeof setTimeout> | null = null;
260
+ const armStall = () => {
261
+ if (stallTimer) {clearTimeout(stallTimer);}
262
+ stallTimer = setTimeout(() => {
263
+ try {
264
+ xhr.abort();
265
+ } catch {
266
+ // already finished
267
+ }
268
+ }, UPLOAD_STALL_MS);
269
+ };
270
+ const disarmStall = () => {
271
+ if (stallTimer) {clearTimeout(stallTimer);}
272
+ stallTimer = null;
273
+ };
274
+ const settledOnload = xhr.onload;
275
+ xhr.onload = (...args: any[]) => {
276
+ disarmStall();
277
+ (settledOnload as any)?.apply(xhr, args);
278
+ };
279
+ const settledOnerror = xhr.onerror;
280
+ xhr.onerror = (...args: any[]) => {
281
+ disarmStall();
282
+ (settledOnerror as any)?.apply(xhr, args);
283
+ };
284
+ const settledOnabort = xhr.onabort;
285
+ xhr.onabort = (...args: any[]) => {
286
+ disarmStall();
287
+ (settledOnabort as any)?.apply(xhr, args);
288
+ };
289
+
290
+ if (xhr.upload) {
291
+ xhr.upload.onprogress = (event: ProgressEvent) => {
292
+ armStall();
293
+ if (!onProgress) {return;}
294
+ if (!event.lengthComputable || !(event.total > 0)) {return;}
295
+ const percent = Math.min(100, Math.max(0, Math.round((event.loaded / event.total) * 100)));
296
+ try {
297
+ onProgress(percent);
298
+ } catch {
299
+ // A progress listener must never fail the upload itself.
300
+ }
301
+ };
302
+ }
303
+
252
304
  xhr.open('POST', url);
253
305
  xhr.setRequestHeader('Authorization', token);
254
306
  xhr.setRequestHeader('Accept', '*/*');
255
307
  // Deliberately no Content-Type — see the note above.
308
+ armStall();
256
309
  xhr.send(body as any);
257
310
  });
258
311
  }
@@ -268,6 +321,8 @@ let secureUploadUnavailable = false;
268
321
 
269
322
  export interface UploadFileOptions {
270
323
  clientEncrypted?: boolean;
324
+ /** Called with 0 to 100 as the request body goes up (XHR upload progress). */
325
+ onProgress?: (percent: number) => void;
271
326
  }
272
327
 
273
328
  export async function uploadFileV2(
@@ -289,7 +344,8 @@ export async function uploadFileV2(
289
344
  return await postMultipart(
290
345
  `${apiRoot}${SECURE_UPLOAD_PATH}`,
291
346
  body,
292
- SECURE_UPLOAD_PATH
347
+ SECURE_UPLOAD_PATH,
348
+ options.onProgress
293
349
  );
294
350
  } catch (error) {
295
351
  if (!canFallBackToLegacyUpload(error)) {throw error;}
@@ -305,7 +361,8 @@ export async function uploadFileV2(
305
361
  return postMultipart(
306
362
  `${apiRoot}${LEGACY_UPLOAD_PATH}`,
307
363
  formData,
308
- LEGACY_UPLOAD_PATH
364
+ LEGACY_UPLOAD_PATH,
365
+ options.onProgress
309
366
  );
310
367
  }
311
368
 
@@ -1,5 +1,6 @@
1
1
  import { Element } from 'ltx';
2
2
  import { store } from '../roomStore';
3
+ import type XmppClient from './xmppClient';
3
4
  import {
4
5
  claimMamResult,
5
6
  collectMamMessage,
@@ -14,6 +15,8 @@ import {
14
15
  editRoomMessage,
15
16
  setComposing,
16
17
  setCurrentRoom,
18
+ setDisplayedMarker,
19
+ setReceivedReceipt,
17
20
  setRoomRole,
18
21
  setReactions,
19
22
  updateRoom,
@@ -31,6 +34,35 @@ import { translateKey } from '../i18n/strings';
31
34
  import { isWindowQueryId } from './xmpp/mamQueryIds';
32
35
  import { loadRoomMembers } from './api-requests/rooms.api';
33
36
  import { subscribeRoomForPush } from '../services/pushSubscriptionService';
37
+ import { isReceiptMarkerId } from '../helpers/readReceipts';
38
+
39
+ const RECEIPTS_NS = 'urn:xmpp:receipts';
40
+ const CHAT_MARKERS_NS = 'urn:xmpp:chat-markers:0';
41
+
42
+ // XEP-0184 / XEP-0333 frames: broadcast to the room, carry no <data>, and are
43
+ // never chat messages (no bubble, no unread, no toast).
44
+ const isReceiptMarkerStanza = (stanza: any): boolean =>
45
+ isReceiptMarkerId(stanza?.attrs?.id) ||
46
+ !!stanza?.getChild?.('received', RECEIPTS_NS) ||
47
+ !!stanza?.getChild?.('displayed', CHAT_MARKERS_NS);
48
+
49
+ // XEP-0184: ack a message from someone else so their client can move its
50
+ // tick to "delivered". Our own echo and system messages are never acked.
51
+ const ackReceivedMessage = (
52
+ message: IMessage,
53
+ roomJID: string,
54
+ stanzaFrom: string | undefined,
55
+ senderJID: string | undefined,
56
+ xmppWs?: XmppClient
57
+ ): void => {
58
+ if (!xmppWs?.sendReceivedReceiptStanza || !message?.id) {return;}
59
+ if (message.isSystemMessage === 'true') {return;}
60
+ // A call-log row is built on this device; the sender has no such message.
61
+ if (String(message.id).startsWith('calllog-')) {return;}
62
+ const user = store.getState().chatSettingStore.user;
63
+ if (isOwnIncomingMessage({ message, stanzaFrom, senderJID, user })) {return;}
64
+ xmppWs.sendReceivedReceiptStanza(String(message.id), roomJID);
65
+ };
34
66
 
35
67
  // TO DO: we are thinking to refactor this code in the following way:
36
68
  // each stanza will be parsed for 'type'
@@ -41,7 +73,8 @@ import { subscribeRoomForPush } from '../services/pushSubscriptionService';
41
73
  // types can be added into our chat protocol (XMPP stanza add field type="") to make it easier to parse here
42
74
 
43
75
  //core default
44
- const onRealtimeMessage = async (stanza: Element) => {
76
+ const onRealtimeMessage = async (stanza: Element, xmppWs?: XmppClient) => {
77
+ if (isReceiptMarkerStanza(stanza)) {return;}
45
78
  if (
46
79
  !stanza?.getChild('result') &&
47
80
  !stanza.getChild('composing') &&
@@ -120,6 +153,7 @@ const onRealtimeMessage = async (stanza: Element) => {
120
153
  message,
121
154
  })
122
155
  );
156
+ ackReceivedMessage(message, roomJID, stanza.attrs.from, senderJID, xmppWs);
123
157
  // A sender missing from usersSet (a big room only carries 30 members)
124
158
  // is resolved lazily; known and recently failed ids are dropped inside.
125
159
  try {
@@ -196,6 +230,38 @@ const onDeleteMessage = async (stanza: Element) => {
196
230
  }
197
231
  };
198
232
 
233
+ // XEP-0184 delivery receipt from an occupant. The real sender travels in the
234
+ // <received> `from` attr, because the stanza's own `from` is the room nick.
235
+ const onReceivedReceipt = (stanza: Element) => {
236
+ if (!String(stanza?.attrs?.id || '').startsWith('message-received')) {return;}
237
+ const received = stanza.getChild('received', RECEIPTS_NS);
238
+ if (!received) {return;}
239
+ const messageId = received.attrs.id;
240
+ const fromXmppUsername = String(received.attrs.from || '').split('@')[0];
241
+ const roomJID = String(stanza.attrs.from || '').split('/')[0];
242
+ if (!messageId || !fromXmppUsername || !roomJID) {return;}
243
+ store.dispatch(
244
+ setReceivedReceipt({
245
+ roomJID,
246
+ messageId,
247
+ fromXmppUsername,
248
+ timestamp: Date.now(),
249
+ })
250
+ );
251
+ };
252
+
253
+ // XEP-0333 read marker from an occupant: one watermark per user and room.
254
+ const onDisplayedMarker = (stanza: Element) => {
255
+ if (!String(stanza?.attrs?.id || '').startsWith('message-displayed')) {return;}
256
+ const displayed = stanza.getChild('displayed', CHAT_MARKERS_NS);
257
+ if (!displayed) {return;}
258
+ const messageId = displayed.attrs.id;
259
+ const xmppUsername = String(displayed.attrs.from || '').split('@')[0];
260
+ const roomJID = String(stanza.attrs.from || '').split('/')[0];
261
+ if (!messageId || !xmppUsername || !roomJID) {return;}
262
+ store.dispatch(setDisplayedMarker({ roomJID, xmppUsername, messageId }));
263
+ };
264
+
199
265
  const onEditMessage = async (stanza: Element) => {
200
266
  if (stanza?.attrs?.id?.includes('edit-message')) {
201
267
  const stanzaId = stanza.getChild('stanza-id');
@@ -225,6 +291,11 @@ const parseMamResult = async (stanza: any): Promise<IMessage | undefined> => {
225
291
  .getChild('result')
226
292
  ?.getChild('forwarded')
227
293
  ?.getChild('message');
294
+ // A receipt or read marker archived with the room is not a message: the
295
+ // page still counts it (the paging cursor follows it), nothing is shown.
296
+ if (isReceiptMarkerStanza(forwardedMsg)) {
297
+ return undefined;
298
+ }
228
299
  if (forwardedMsg?.getChild?.('reactions')) {
229
300
  const reaction = extractReaction(
230
301
  forwardedMsg,
@@ -341,7 +412,7 @@ const parseMamResult = async (stanza: any): Promise<IMessage | undefined> => {
341
412
  }
342
413
  };
343
414
 
344
- const onMessageHistory = async (stanza: any) => {
415
+ const onMessageHistory = async (stanza: any, xmppWs?: XmppClient) => {
345
416
  if (!isMamResult(stanza)) {return;}
346
417
  // A windowed query (a jump target's neighbourhood, a time lookup) belongs
347
418
  // to the caller that asked: its rows are collected and returned there and
@@ -356,6 +427,21 @@ const onMessageHistory = async (stanza: any) => {
356
427
  collectMamMessage(stanza, message);
357
428
  }
358
429
  }
430
+ if (message) {
431
+ // A message fetched by catch-up never reaches the live ack, so it is
432
+ // acked here (the client remembers which ones it already acked).
433
+ const innerFrom = stanza
434
+ .getChild('result')
435
+ ?.getChild('forwarded')
436
+ ?.getChild('message')?.attrs?.from;
437
+ ackReceivedMessage(
438
+ message,
439
+ String(stanza.attrs?.from || '').split('/')[0],
440
+ innerFrom,
441
+ undefined,
442
+ xmppWs
443
+ );
444
+ }
359
445
  if (!claimed && message) {
360
446
  store.dispatch(
361
447
  addRoomMessage({
@@ -677,6 +763,9 @@ const onRoomKicked = (stanza: Element | any) => {
677
763
  export {
678
764
  onRealtimeMessage,
679
765
  onMessageHistory,
766
+ onReceivedReceipt,
767
+ onDisplayedMarker,
768
+ isReceiptMarkerStanza,
680
769
  onPresenceInRoom,
681
770
  onGetLastMessageArchive,
682
771
  handleComposing,
@@ -5,6 +5,8 @@ import {
5
5
  onEditMessage,
6
6
  onRealtimeMessage,
7
7
  onMessageHistory,
8
+ onReceivedReceipt,
9
+ onDisplayedMarker,
8
10
  onGetLastMessageArchive,
9
11
  handleComposing,
10
12
  onChatInvite,
@@ -127,8 +129,10 @@ function dispatchStanza(stanza: Element, xmppWs: XmppClient) {
127
129
  onDeleteMessage(unwrapped);
128
130
  onEditMessage(unwrapped);
129
131
  onChatInvite(unwrapped, xmppWs);
130
- onRealtimeMessage(unwrapped);
131
- onMessageHistory(unwrapped);
132
+ onReceivedReceipt(unwrapped);
133
+ onDisplayedMarker(unwrapped);
134
+ onRealtimeMessage(unwrapped, xmppWs);
135
+ onMessageHistory(unwrapped, xmppWs);
132
136
  handleComposing(unwrapped, xmppWs.username);
133
137
  onPresenceInRoom(unwrapped);
134
138
  break;
@@ -0,0 +1,31 @@
1
+ import { Client, xml } from '@xmpp/client';
2
+
3
+ // XEP-0333 read marker, broadcast to the room (same convention as
4
+ // sendReceivedReceipt.xmpp.ts / sendMessageReaction.xmpp.ts). `messageId` is
5
+ // the id of the newest message the user has displayed - a single watermark
6
+ // per room, not one marker per message.
7
+ export function sendDisplayedMarker(
8
+ client: Client,
9
+ messageId: string,
10
+ roomJid: string
11
+ ) {
12
+ const id = `message-displayed:${Date.now().toString()}`;
13
+
14
+ const message = xml(
15
+ 'message',
16
+ {
17
+ id,
18
+ type: 'groupchat',
19
+ from: client.jid?.toString(),
20
+ to: roomJid,
21
+ },
22
+ xml('displayed', {
23
+ xmlns: 'urn:xmpp:chat-markers:0',
24
+ id: messageId,
25
+ from: client.jid?.toString(),
26
+ }),
27
+ xml('store', { xmlns: 'urn:xmpp:hints' })
28
+ );
29
+
30
+ client.send(message);
31
+ }
@@ -1,5 +1,8 @@
1
1
  import { Client, xml } from '@xmpp/client';
2
2
  import { sendSealedMedia, shouldEncrypt } from '../../e2ee/send';
3
+ import type { IAttachment, IMentionSpan } from '../../types/types';
4
+ import { serializeAttachments } from '../../helpers/attachments';
5
+ import { normalizeCaptionInput } from '../../helpers/caption';
3
6
 
4
7
  // Monotonic counter — see sendTextMessage for the rationale (avoids
5
8
  // Date.now() id collisions on <1ms-apart sends).
@@ -20,6 +23,18 @@ const toServiceXmlns = (devServer: string | undefined, client: Client): string =
20
23
  return `wss://${ds.replace(/^\/+|\/+$/g, '')}/ws`;
21
24
  };
22
25
 
26
+ /**
27
+ * One media stanza for one or more files.
28
+ *
29
+ * `data` carries the flat fields that describe attachment #0, so a client
30
+ * that predates multi-attach still shows the first file. With more than one
31
+ * file the whole list rides on `<data attachments='JSON'>` (selection order).
32
+ *
33
+ * A typed caption rides on the message too: as `caption` / `captionMentions`
34
+ * attributes in a plain room, and inside the sealed <body> in an e2ee room
35
+ * (never as an attribute there). A message with one file and no caption is
36
+ * byte-identical to what this function produced before multi-attach.
37
+ */
23
38
  export function sendMediaMessage(
24
39
  client: Client,
25
40
  roomJID: string,
@@ -30,7 +45,7 @@ export function sendMediaMessage(
30
45
  const id =
31
46
  customId || `send-media-message-${Date.now()}-${(_sendMediaSeq = (_sendMediaSeq + 1) >>> 0)}`;
32
47
 
33
- const dataToSend = {
48
+ const dataToSend: Record<string, unknown> = {
34
49
  xmlns: toServiceXmlns(devServer, client),
35
50
  senderJID: client.jid?.toString(),
36
51
  senderFirstName: data.firstName,
@@ -63,23 +78,65 @@ export function sendMediaMessage(
63
78
  push: 'true',
64
79
  };
65
80
 
81
+ // Only stamped when there is more than one file, so a single-file stanza
82
+ // keeps exactly the attributes it always had.
83
+ const files: IAttachment[] = Array.isArray(data?.attachments) ? data.attachments : [];
84
+ if (files.length > 1) {
85
+ // The flat fields describe attachment #0, whatever the caller passed, so
86
+ // an older client shows the first file of the message.
87
+ const head = files[0]!;
88
+ const flat: Array<keyof IAttachment> = [
89
+ 'attachmentId',
90
+ 'location',
91
+ 'locationPreview',
92
+ 'mimetype',
93
+ 'originalName',
94
+ 'fileName',
95
+ 'size',
96
+ 'duration',
97
+ ];
98
+ for (const key of flat) {
99
+ const value = head[key];
100
+ if (value !== undefined && value !== null && value !== '') {
101
+ dataToSend[key] = value;
102
+ }
103
+ }
104
+ dataToSend.attachments = serializeAttachments(files);
105
+ }
106
+
66
107
  const keys: string[] | undefined = data?.e2eeKeys;
67
108
  if (keys?.length) {
68
- (dataToSend as Record<string, unknown>).clientEncrypted = 'true';
109
+ dataToSend.clientEncrypted = 'true';
69
110
  }
111
+
112
+ const caption = normalizeCaptionInput({
113
+ text: typeof data?.caption === 'string' ? data.caption : '',
114
+ mentions: data?.captionMentions as IMentionSpan[] | undefined,
115
+ });
70
116
  const hints = xml('store', { xmlns: 'urn:xmpp:hints' });
71
117
 
72
118
  if (keys?.length && shouldEncrypt(roomJID)) {
119
+ // The caption is sealed with the keys. It is never copied onto <data>.
120
+ const sealedBody = caption
121
+ ? { v: 1, keys, caption: caption.caption, ...(caption.mentions ? { captionMentions: caption.mentions } : {}) }
122
+ : { v: 1, keys };
73
123
  sendSealedMedia(
74
124
  client,
75
125
  roomJID,
76
126
  id,
77
- xml('body', {}, JSON.stringify({ v: 1, keys })),
127
+ xml('body', {}, JSON.stringify(sealedBody)),
78
128
  [xml('data', dataToSend), hints]
79
129
  );
80
130
  return id;
81
131
  }
82
132
 
133
+ if (caption) {
134
+ dataToSend.caption = caption.caption;
135
+ if (caption.mentions) {
136
+ dataToSend.captionMentions = JSON.stringify(caption.mentions);
137
+ }
138
+ }
139
+
83
140
  const message = xml(
84
141
  'message',
85
142
  {
@@ -0,0 +1,31 @@
1
+ import { Client, xml } from '@xmpp/client';
2
+
3
+ // XEP-0184 delivery receipt, broadcast to the room (mirrors
4
+ // sendMessageReaction.xmpp.ts) so every occupant can render tick state.
5
+ // The top-level stanza `from` gets rewritten to the room/nick by the MUC
6
+ // server, so the real xmppUsername travels in the <received> `from` attr.
7
+ export function sendReceivedReceipt(
8
+ client: Client,
9
+ messageId: string,
10
+ roomJid: string
11
+ ) {
12
+ const id = `message-received:${Date.now().toString()}`;
13
+
14
+ const message = xml(
15
+ 'message',
16
+ {
17
+ id,
18
+ type: 'groupchat',
19
+ from: client.jid?.toString(),
20
+ to: roomJid,
21
+ },
22
+ xml('received', {
23
+ xmlns: 'urn:xmpp:receipts',
24
+ id: messageId,
25
+ from: client.jid?.toString(),
26
+ }),
27
+ xml('store', { xmlns: 'urn:xmpp:hints' })
28
+ );
29
+
30
+ client.send(message);
31
+ }