@ethora/chat-component-rn 26.10.5 → 26.10.7

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 (426) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +18 -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/helpers/secureStoreRuntime.js +10 -1
  81. package/lib/commonjs/helpers/secureStoreRuntime.js.map +1 -1
  82. package/lib/commonjs/hooks/useSendMessage.js +384 -193
  83. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  84. package/lib/commonjs/i18n/strings.js +96 -0
  85. package/lib/commonjs/i18n/strings.js.map +1 -1
  86. package/lib/commonjs/main.js +81 -0
  87. package/lib/commonjs/main.js.map +1 -1
  88. package/lib/commonjs/networking/api-requests/auth.api.js +62 -3
  89. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  90. package/lib/commonjs/networking/api-requests/push.api.js +22 -7
  91. package/lib/commonjs/networking/api-requests/push.api.js.map +1 -1
  92. package/lib/commonjs/networking/stanzaHandlers.js +100 -3
  93. package/lib/commonjs/networking/stanzaHandlers.js.map +1 -1
  94. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +4 -2
  95. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  96. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js +28 -0
  97. package/lib/commonjs/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  98. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  99. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  100. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js +28 -0
  101. package/lib/commonjs/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  102. package/lib/commonjs/networking/xmppClient.js +64 -0
  103. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  104. package/lib/commonjs/push/pushKeyStore.js +169 -0
  105. package/lib/commonjs/push/pushKeyStore.js.map +1 -0
  106. package/lib/commonjs/push/sealedBox.js +106 -0
  107. package/lib/commonjs/push/sealedBox.js.map +1 -0
  108. package/lib/commonjs/push/sealedPush.js +124 -0
  109. package/lib/commonjs/push/sealedPush.js.map +1 -0
  110. package/lib/commonjs/push/sealedPushTask.js +195 -0
  111. package/lib/commonjs/push/sealedPushTask.js.map +1 -0
  112. package/lib/commonjs/roomStore/persistence.js +78 -5
  113. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  114. package/lib/commonjs/roomStore/roomHeapSlice.js +4 -0
  115. package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
  116. package/lib/commonjs/roomStore/roomsSlice.js +114 -1
  117. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  118. package/lib/commonjs/services/pushRegistration.js +12 -7
  119. package/lib/commonjs/services/pushRegistration.js.map +1 -1
  120. package/lib/commonjs/types/models/attachment.model.js +2 -0
  121. package/lib/commonjs/types/models/attachment.model.js.map +1 -0
  122. package/lib/commonjs/version.js +1 -1
  123. package/lib/module/assets/icons.js +1 -1
  124. package/lib/module/assets/icons.js.map +1 -1
  125. package/lib/module/components/MainComponents/AlbumGrid.js +146 -0
  126. package/lib/module/components/MainComponents/AlbumGrid.js.map +1 -0
  127. package/lib/module/components/MainComponents/ChatRoom.js +2 -2
  128. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  129. package/lib/module/components/MainComponents/MediaMessage.js +200 -124
  130. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  131. package/lib/module/components/MainComponents/MediaUploadOverlay.js +104 -0
  132. package/lib/module/components/MainComponents/MediaUploadOverlay.js.map +1 -0
  133. package/lib/module/components/MessageBubble/MediaCaption.js +46 -0
  134. package/lib/module/components/MessageBubble/MediaCaption.js.map +1 -0
  135. package/lib/module/components/MessageBubble/Message.js +44 -5
  136. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  137. package/lib/module/components/MessageBubble/MessageInteractions.js +2 -1
  138. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  139. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +75 -0
  140. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  141. package/lib/module/components/RoomComponents/LastMessageItem.js +20 -4
  142. package/lib/module/components/RoomComponents/LastMessageItem.js.map +1 -1
  143. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js +3 -2
  144. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  145. package/lib/module/components/RoomComponents/styled/LastMessageFile.js +3 -2
  146. package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  147. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +3 -2
  148. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  149. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +3 -2
  150. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  151. package/lib/module/components/Thread/ThreadWrapper.js +3 -2
  152. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  153. package/lib/module/components/styled/MediaFilePreview.js +61 -48
  154. package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
  155. package/lib/module/components/styled/MessageImage.js +16 -6
  156. package/lib/module/components/styled/MessageImage.js.map +1 -1
  157. package/lib/module/components/styled/SendInput.js +99 -68
  158. package/lib/module/components/styled/SendInput.js.map +1 -1
  159. package/lib/module/components/styled/VideoMessage.js +24 -10
  160. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  161. package/lib/module/context/xmppProvider.js +46 -0
  162. package/lib/module/context/xmppProvider.js.map +1 -1
  163. package/lib/module/e2ee/fileEnvelope.js +1 -1
  164. package/lib/module/e2ee/fileEnvelope.js.map +1 -1
  165. package/lib/module/e2ee/sealedBody.js +32 -2
  166. package/lib/module/e2ee/sealedBody.js.map +1 -1
  167. package/lib/module/helpers/albumLayout.js +143 -0
  168. package/lib/module/helpers/albumLayout.js.map +1 -0
  169. package/lib/module/helpers/attachments.js +245 -0
  170. package/lib/module/helpers/attachments.js.map +1 -0
  171. package/lib/module/helpers/caption.js +207 -0
  172. package/lib/module/helpers/caption.js.map +1 -0
  173. package/lib/module/helpers/createMainMessageForThread.js +5 -1
  174. package/lib/module/helpers/createMainMessageForThread.js.map +1 -1
  175. package/lib/module/helpers/createMessageFromXml.js +20 -6
  176. package/lib/module/helpers/createMessageFromXml.js.map +1 -1
  177. package/lib/module/helpers/fileKind.js +34 -0
  178. package/lib/module/helpers/fileKind.js.map +1 -0
  179. package/lib/module/helpers/getDataFromXml.js +6 -1
  180. package/lib/module/helpers/getDataFromXml.js.map +1 -1
  181. package/lib/module/helpers/mapWithConcurrency.js +37 -0
  182. package/lib/module/helpers/mapWithConcurrency.js.map +1 -0
  183. package/lib/module/helpers/mediaLimits.js +27 -0
  184. package/lib/module/helpers/mediaLimits.js.map +1 -0
  185. package/lib/module/helpers/mediaSendRetry.js +55 -0
  186. package/lib/module/helpers/mediaSendRetry.js.map +1 -0
  187. package/lib/module/helpers/mediaWireData.js +58 -0
  188. package/lib/module/helpers/mediaWireData.js.map +1 -0
  189. package/lib/module/helpers/mentionSpans.js +14 -0
  190. package/lib/module/helpers/mentionSpans.js.map +1 -0
  191. package/lib/module/helpers/mimeToExtension.js +43 -0
  192. package/lib/module/helpers/mimeToExtension.js.map +1 -1
  193. package/lib/module/helpers/readReceipts.js +160 -0
  194. package/lib/module/helpers/readReceipts.js.map +1 -0
  195. package/lib/module/helpers/repairFilenameEncoding.js +74 -0
  196. package/lib/module/helpers/repairFilenameEncoding.js.map +1 -0
  197. package/lib/module/helpers/safeJson.js +13 -0
  198. package/lib/module/helpers/safeJson.js.map +1 -0
  199. package/lib/module/helpers/secureStoreRuntime.js +10 -1
  200. package/lib/module/helpers/secureStoreRuntime.js.map +1 -1
  201. package/lib/module/hooks/useSendMessage.js +387 -195
  202. package/lib/module/hooks/useSendMessage.js.map +1 -1
  203. package/lib/module/i18n/strings.js +96 -0
  204. package/lib/module/i18n/strings.js.map +1 -1
  205. package/lib/module/main.js +6 -0
  206. package/lib/module/main.js.map +1 -1
  207. package/lib/module/networking/api-requests/auth.api.js +62 -3
  208. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  209. package/lib/module/networking/api-requests/push.api.js +21 -6
  210. package/lib/module/networking/api-requests/push.api.js.map +1 -1
  211. package/lib/module/networking/stanzaHandlers.js +97 -4
  212. package/lib/module/networking/stanzaHandlers.js.map +1 -1
  213. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +5 -3
  214. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  215. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js +25 -0
  216. package/lib/module/networking/xmpp/sendDisplayedMarker.xmpp.js.map +1 -0
  217. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js +53 -2
  218. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  219. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js +25 -0
  220. package/lib/module/networking/xmpp/sendReceivedReceipt.xmpp.js.map +1 -0
  221. package/lib/module/networking/xmppClient.js +64 -0
  222. package/lib/module/networking/xmppClient.js.map +1 -1
  223. package/lib/module/push/pushKeyStore.js +159 -0
  224. package/lib/module/push/pushKeyStore.js.map +1 -0
  225. package/lib/module/push/sealedBox.js +97 -0
  226. package/lib/module/push/sealedBox.js.map +1 -0
  227. package/lib/module/push/sealedPush.js +117 -0
  228. package/lib/module/push/sealedPush.js.map +1 -0
  229. package/lib/module/push/sealedPushTask.js +187 -0
  230. package/lib/module/push/sealedPushTask.js.map +1 -0
  231. package/lib/module/roomStore/persistence.js +78 -5
  232. package/lib/module/roomStore/persistence.js.map +1 -1
  233. package/lib/module/roomStore/roomHeapSlice.js +4 -0
  234. package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
  235. package/lib/module/roomStore/roomsSlice.js +109 -0
  236. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  237. package/lib/module/services/pushRegistration.js +12 -7
  238. package/lib/module/services/pushRegistration.js.map +1 -1
  239. package/lib/module/types/models/attachment.model.js +2 -0
  240. package/lib/module/types/models/attachment.model.js.map +1 -0
  241. package/lib/module/version.js +1 -1
  242. package/lib/typescript/assets/icons.d.ts +6 -1
  243. package/lib/typescript/assets/icons.d.ts.map +1 -1
  244. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts +21 -0
  245. package/lib/typescript/components/MainComponents/AlbumGrid.d.ts.map +1 -0
  246. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  247. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  248. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts +17 -0
  249. package/lib/typescript/components/MainComponents/MediaUploadOverlay.d.ts.map +1 -0
  250. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts +12 -0
  251. package/lib/typescript/components/MessageBubble/MediaCaption.d.ts.map +1 -0
  252. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  253. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  254. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  255. package/lib/typescript/components/RoomComponents/LastMessageItem.d.ts.map +1 -1
  256. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts +2 -0
  257. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts.map +1 -1
  258. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts +2 -0
  259. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
  260. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts +2 -0
  261. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
  262. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts +2 -0
  263. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
  264. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  265. package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
  266. package/lib/typescript/components/styled/MessageImage.d.ts +10 -0
  267. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  268. package/lib/typescript/components/styled/SendInput.d.ts +7 -1
  269. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  270. package/lib/typescript/components/styled/VideoMessage.d.ts +7 -0
  271. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  272. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  273. package/lib/typescript/e2ee/fileEnvelope.d.ts +1 -1
  274. package/lib/typescript/e2ee/fileEnvelope.d.ts.map +1 -1
  275. package/lib/typescript/e2ee/sealedBody.d.ts +16 -0
  276. package/lib/typescript/e2ee/sealedBody.d.ts.map +1 -1
  277. package/lib/typescript/helpers/albumLayout.d.ts +66 -0
  278. package/lib/typescript/helpers/albumLayout.d.ts.map +1 -0
  279. package/lib/typescript/helpers/attachments.d.ts +55 -0
  280. package/lib/typescript/helpers/attachments.d.ts.map +1 -0
  281. package/lib/typescript/helpers/caption.d.ts +64 -0
  282. package/lib/typescript/helpers/caption.d.ts.map +1 -0
  283. package/lib/typescript/helpers/createMainMessageForThread.d.ts.map +1 -1
  284. package/lib/typescript/helpers/createMessageFromXml.d.ts.map +1 -1
  285. package/lib/typescript/helpers/fileKind.d.ts +20 -0
  286. package/lib/typescript/helpers/fileKind.d.ts.map +1 -0
  287. package/lib/typescript/helpers/getDataFromXml.d.ts.map +1 -1
  288. package/lib/typescript/helpers/mapWithConcurrency.d.ts +9 -0
  289. package/lib/typescript/helpers/mapWithConcurrency.d.ts.map +1 -0
  290. package/lib/typescript/helpers/mediaLimits.d.ts +13 -0
  291. package/lib/typescript/helpers/mediaLimits.d.ts.map +1 -0
  292. package/lib/typescript/helpers/mediaSendRetry.d.ts +26 -0
  293. package/lib/typescript/helpers/mediaSendRetry.d.ts.map +1 -0
  294. package/lib/typescript/helpers/mediaWireData.d.ts +16 -0
  295. package/lib/typescript/helpers/mediaWireData.d.ts.map +1 -0
  296. package/lib/typescript/helpers/mentionSpans.d.ts +8 -0
  297. package/lib/typescript/helpers/mentionSpans.d.ts.map +1 -0
  298. package/lib/typescript/helpers/mimeToExtension.d.ts +7 -0
  299. package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -1
  300. package/lib/typescript/helpers/readReceipts.d.ts +82 -0
  301. package/lib/typescript/helpers/readReceipts.d.ts.map +1 -0
  302. package/lib/typescript/helpers/repairFilenameEncoding.d.ts +12 -0
  303. package/lib/typescript/helpers/repairFilenameEncoding.d.ts.map +1 -0
  304. package/lib/typescript/helpers/safeJson.d.ts +2 -0
  305. package/lib/typescript/helpers/safeJson.d.ts.map +1 -0
  306. package/lib/typescript/helpers/secureStoreRuntime.d.ts +17 -3
  307. package/lib/typescript/helpers/secureStoreRuntime.d.ts.map +1 -1
  308. package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
  309. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  310. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  311. package/lib/typescript/main.d.ts +6 -0
  312. package/lib/typescript/main.d.ts.map +1 -1
  313. package/lib/typescript/networking/api-requests/auth.api.d.ts +2 -0
  314. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  315. package/lib/typescript/networking/api-requests/push.api.d.ts +14 -2
  316. package/lib/typescript/networking/api-requests/push.api.d.ts.map +1 -1
  317. package/lib/typescript/networking/stanzaHandlers.d.ts +7 -3
  318. package/lib/typescript/networking/stanzaHandlers.d.ts.map +1 -1
  319. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  320. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts +3 -0
  321. package/lib/typescript/networking/xmpp/sendDisplayedMarker.xmpp.d.ts.map +1 -0
  322. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts +12 -0
  323. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts.map +1 -1
  324. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts +3 -0
  325. package/lib/typescript/networking/xmpp/sendReceivedReceipt.xmpp.d.ts.map +1 -0
  326. package/lib/typescript/networking/xmppClient.d.ts +15 -0
  327. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  328. package/lib/typescript/push/pushKeyStore.d.ts +35 -0
  329. package/lib/typescript/push/pushKeyStore.d.ts.map +1 -0
  330. package/lib/typescript/push/sealedBox.d.ts +24 -0
  331. package/lib/typescript/push/sealedBox.d.ts.map +1 -0
  332. package/lib/typescript/push/sealedPush.d.ts +61 -0
  333. package/lib/typescript/push/sealedPush.d.ts.map +1 -0
  334. package/lib/typescript/push/sealedPushTask.d.ts +33 -0
  335. package/lib/typescript/push/sealedPushTask.d.ts.map +1 -0
  336. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  337. package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
  338. package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
  339. package/lib/typescript/roomStore/roomsSlice.d.ts +67 -3
  340. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  341. package/lib/typescript/services/pushRegistration.d.ts +8 -0
  342. package/lib/typescript/services/pushRegistration.d.ts.map +1 -1
  343. package/lib/typescript/types/models/attachment.model.d.ts +52 -0
  344. package/lib/typescript/types/models/attachment.model.d.ts.map +1 -0
  345. package/lib/typescript/types/models/message.model.d.ts +26 -0
  346. package/lib/typescript/types/models/message.model.d.ts.map +1 -1
  347. package/lib/typescript/types/models/modal.model.d.ts +7 -0
  348. package/lib/typescript/types/models/modal.model.d.ts.map +1 -1
  349. package/lib/typescript/types/models/room.model.d.ts +2 -0
  350. package/lib/typescript/types/models/room.model.d.ts.map +1 -1
  351. package/lib/typescript/types/types.d.ts +35 -1
  352. package/lib/typescript/types/types.d.ts.map +1 -1
  353. package/lib/typescript/version.d.ts +1 -1
  354. package/package.json +14 -1
  355. package/plugins/notification-service/Info.plist +31 -0
  356. package/plugins/notification-service/NotificationService.swift +195 -0
  357. package/plugins/notification-service/SealedBox.swift +355 -0
  358. package/plugins/notification-service/check/gen-vectors.mjs +24 -0
  359. package/plugins/notification-service/check/main.swift +37 -0
  360. package/plugins/notification-service/check/run.sh +12 -0
  361. package/plugins/notification-service/check/vectors.json +1 -0
  362. package/plugins/withNotificationServiceExtension.js +189 -0
  363. package/src/assets/icons.tsx +20 -2
  364. package/src/components/MainComponents/AlbumGrid.tsx +183 -0
  365. package/src/components/MainComponents/ChatRoom.tsx +4 -2
  366. package/src/components/MainComponents/MediaMessage.tsx +246 -130
  367. package/src/components/MainComponents/MediaUploadOverlay.tsx +102 -0
  368. package/src/components/MessageBubble/MediaCaption.tsx +45 -0
  369. package/src/components/MessageBubble/Message.tsx +71 -3
  370. package/src/components/MessageBubble/MessageInteractions.tsx +2 -1
  371. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +64 -0
  372. package/src/components/RoomComponents/LastMessageItem.tsx +15 -4
  373. package/src/components/RoomComponents/styled/LastAudioMessage.tsx +6 -3
  374. package/src/components/RoomComponents/styled/LastMessageFile.tsx +6 -2
  375. package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +6 -2
  376. package/src/components/RoomComponents/styled/LastMessageVideo.tsx +6 -2
  377. package/src/components/Thread/ThreadWrapper.tsx +5 -9
  378. package/src/components/styled/MediaFilePreview.tsx +49 -34
  379. package/src/components/styled/MessageImage.tsx +21 -5
  380. package/src/components/styled/SendInput.tsx +101 -87
  381. package/src/components/styled/VideoMessage.tsx +34 -9
  382. package/src/context/xmppProvider.tsx +32 -0
  383. package/src/e2ee/fileEnvelope.ts +1 -1
  384. package/src/e2ee/sealedBody.ts +44 -5
  385. package/src/helpers/albumLayout.ts +207 -0
  386. package/src/helpers/attachments.ts +316 -0
  387. package/src/helpers/caption.ts +283 -0
  388. package/src/helpers/createMainMessageForThread.ts +6 -1
  389. package/src/helpers/createMessageFromXml.ts +21 -5
  390. package/src/helpers/fileKind.ts +38 -0
  391. package/src/helpers/getDataFromXml.ts +5 -1
  392. package/src/helpers/mapWithConcurrency.ts +32 -0
  393. package/src/helpers/mediaLimits.ts +27 -0
  394. package/src/helpers/mediaSendRetry.ts +59 -0
  395. package/src/helpers/mediaWireData.ts +57 -0
  396. package/src/helpers/mentionSpans.ts +17 -0
  397. package/src/helpers/mimeToExtension.ts +51 -0
  398. package/src/helpers/readReceipts.ts +219 -0
  399. package/src/helpers/repairFilenameEncoding.ts +82 -0
  400. package/src/helpers/safeJson.ts +11 -0
  401. package/src/helpers/secureStoreRuntime.ts +21 -3
  402. package/src/hooks/useSendMessage.tsx +448 -249
  403. package/src/i18n/strings.ts +96 -0
  404. package/src/main.ts +30 -0
  405. package/src/networking/api-requests/auth.api.ts +60 -3
  406. package/src/networking/api-requests/push.api.ts +29 -4
  407. package/src/networking/stanzaHandlers.ts +91 -2
  408. package/src/networking/xmpp/handleStanzas.xmpp.ts +6 -2
  409. package/src/networking/xmpp/sendDisplayedMarker.xmpp.ts +31 -0
  410. package/src/networking/xmpp/sendMediaMessage.xmpp.ts +60 -3
  411. package/src/networking/xmpp/sendReceivedReceipt.xmpp.ts +31 -0
  412. package/src/networking/xmppClient.ts +48 -0
  413. package/src/push/pushKeyStore.ts +163 -0
  414. package/src/push/sealedBox.ts +104 -0
  415. package/src/push/sealedPush.ts +127 -0
  416. package/src/push/sealedPushTask.ts +175 -0
  417. package/src/roomStore/persistence.ts +94 -3
  418. package/src/roomStore/roomHeapSlice.ts +23 -1
  419. package/src/roomStore/roomsSlice.ts +112 -1
  420. package/src/services/pushRegistration.ts +25 -5
  421. package/src/types/models/attachment.model.ts +51 -0
  422. package/src/types/models/message.model.ts +29 -0
  423. package/src/types/models/modal.model.ts +7 -0
  424. package/src/types/models/room.model.ts +2 -0
  425. package/src/types/types.ts +41 -1
  426. package/src/version.ts +1 -1
@@ -36,6 +36,13 @@ interface CustomMessageImageProps {
36
36
  * route; rendering it inline is what left the bubbles blank.
37
37
  */
38
38
  locationPreview?: string;
39
+ /**
40
+ * Album mode: fill a tile of exactly this size (cover, square corners; the
41
+ * tile clips) instead of sizing itself to the picture.
42
+ */
43
+ tile?: { width: number; height: number };
44
+ /** Replaces the default "open this one file" tap, e.g. to open an album at an index. */
45
+ onOpen?: () => void;
39
46
  }
40
47
 
41
48
  /** Keep the personal file token out of the logs. */
@@ -51,6 +58,8 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
51
58
  originalName,
52
59
  mimetype,
53
60
  locationPreview,
61
+ tile,
62
+ onOpen,
54
63
  }) => {
55
64
  // Preview first, original only as a fallback — mirrors the web client,
56
65
  // where the <img> src is the preview and `fileURL` is reserved for the
@@ -59,12 +68,13 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
59
68
  const [loading, setLoading] = useState(true);
60
69
  // Start with a neutral box; swap to the real aspect ratio once the
61
70
  // image's natural dimensions are known so it's shown scaled, not cropped.
62
- const [dims, setDims] = useState<MediaDims>(defaultMediaDims());
71
+ const [measured, setDims] = useState<MediaDims>(defaultMediaDims());
72
+ const dims: MediaDims = tile ?? measured;
63
73
 
64
74
  const dispatch = useDispatch();
65
75
 
66
76
  useEffect(() => {
67
- if (!displayURL) {
77
+ if (!displayURL || tile) {
68
78
  return;
69
79
  }
70
80
  let active = true;
@@ -88,15 +98,21 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
88
98
  return () => {
89
99
  active = false;
90
100
  };
91
- }, [displayURL]);
101
+ }, [displayURL, tile]);
92
102
 
93
103
  const handleOpen = () => {
104
+ if (onOpen) {
105
+ onOpen();
106
+ return;
107
+ }
94
108
  dispatch(setActiveFile({ fileName, fileURL, mimetype, originalName }));
95
109
  dispatch(setActiveModal(MODAL_TYPES.FILE_PREVIEW));
96
110
  };
97
111
 
98
112
  return (
99
- <Container style={{ width: dims.width }}>
113
+ <Container
114
+ style={tile ? { width: dims.width, borderRadius: 0 } : { width: dims.width }}
115
+ >
100
116
  <TouchableOpacity onPress={handleOpen} activeOpacity={0.9}>
101
117
  {loading && (
102
118
  <View
@@ -117,7 +133,7 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
117
133
  style={{
118
134
  width: dims.width,
119
135
  height: dims.height,
120
- borderRadius: 10,
136
+ borderRadius: tile ? 0 : 10,
121
137
  backgroundColor: 'rgba(0, 0, 0, 0)',
122
138
  }}
123
139
  onLoadEnd={() => setLoading(false)}
@@ -17,6 +17,7 @@ import { getElementFont } from '../../helpers/getElementFont';
17
17
  import { useTheme } from '../../hooks/useTheme';
18
18
  import { useT } from '../../i18n/useT';
19
19
  import { getMediaLibrary } from '../../helpers/mediaLibraryRuntime';
20
+ import { mediaFileCap, mediaSizeLimit } from '../../helpers/mediaLimits';
20
21
  import * as ImagePicker from 'expo-image-picker';
21
22
  import * as DocumentPicker from 'expo-document-picker';
22
23
  import * as ImageManipulator from 'expo-image-manipulator';
@@ -44,14 +45,6 @@ import {
44
45
  // receivers draw it without downloading and decoding the clip first.
45
46
  const VOICE_PRESET = { ...RecordingPresets.HIGH_QUALITY, isMeteringEnabled: true };
46
47
 
47
- // iOS photos default to HEIC; many web backends (incl. ours) 500 on
48
- // HEIC uploads because they can't decode it. Convert to JPEG before
49
- // upload — JPEG is universally supported. JPEGs pass through.
50
- // Server rejects uploads over its body-size cap with HTTP 413 (Request
51
- // Entity Too Large). Guard client-side so the user gets a clear message
52
- // instead of a silent failed bubble. 50 MB matches the backend limit.
53
- const MAX_MEDIA_BYTES = 50 * 1024 * 1024;
54
-
55
48
  const formatBytes = (bytes: number): string => {
56
49
  if (bytes >= 1024 * 1024) {
57
50
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
@@ -89,7 +82,11 @@ interface SendInputProps {
89
82
  sendMessage: (message: string) => void;
90
83
  isLoading: boolean;
91
84
  editMessage?: string;
92
- sendMedia: (data: any, type: string) => void;
85
+ /**
86
+ * Sends one message carrying `data` (a file or the picked list). The typed
87
+ * text, when there is any, goes with it as the caption.
88
+ */
89
+ sendMedia: (data: any, type: string, caption?: { text: string }) => unknown;
93
90
  config?: IConfig;
94
91
  onFocus?: () => void;
95
92
  onBlur?: () => void;
@@ -164,25 +161,64 @@ const SendInput: React.FC<SendInputProps> = ({
164
161
  // "Document" row or a dev-mode double-run of the sheet's close callback.
165
162
  const pickingDocRef = useRef(false);
166
163
 
167
- const handleFileSelect = (files: MediaFile[]) => {
168
- // Enforce the upload size cap before anything reaches the network.
169
- // The backend rejects oversized bodies with HTTP 413; catching it
170
- // here gives a clear message instead of a silent failed bubble.
171
- const limit = maxMediaBytes ?? MAX_MEDIA_BYTES;
172
- const oversize = files.find(
173
- (f) => typeof f.size === 'number' && f.size > limit
174
- );
175
- if (oversize) {
176
- Alert.alert(
177
- 'File too large',
178
- `"${oversize.name}" is ${formatBytes(oversize.size!)}. The maximum allowed size is ${Math.round(
179
- limit / (1024 * 1024)
180
- )} MB.`
181
- );
182
- return;
164
+ /**
165
+ * Adds picked files to the tray. The tray is a list, not a slot: a second
166
+ * pick or a camera shot appends. Enforces the per-message file cap and the
167
+ * per-file size cap before anything reaches the network; the size cap is
168
+ * the upload limit (50 MB, or the sealed-file limit in an e2ee room),
169
+ * lowered by `config.attachments.maxSizeMB`. The backend rejects oversized
170
+ * bodies with HTTP 413, so catching them here gives a clear message.
171
+ */
172
+ const handleFileSelect = (incoming: MediaFile[]) => {
173
+ if (incoming.length === 0) {return;}
174
+ const limit = mediaSizeLimit(config, maxMediaBytes);
175
+ const fitting = incoming.filter((file) => {
176
+ if (typeof file.size === 'number' && file.size > limit) {
177
+ Alert.alert(
178
+ t('attachment.tooLarge', {
179
+ name: file.name,
180
+ max: formatBytes(limit),
181
+ })
182
+ );
183
+ return false;
184
+ }
185
+ return true;
186
+ });
187
+ const cap = mediaFileCap(config);
188
+ const room = cap - filePreviewsRef.current.length;
189
+ if (fitting.length === 0) {return;}
190
+ if (room <= 0 || fitting.length > room) {
191
+ Alert.alert(t('attachment.limit', { count: cap }));
183
192
  }
184
- filePreviewsRef.current = files;
185
- setFilePreviews([...files]);
193
+ const accepted = fitting.slice(0, Math.max(0, room));
194
+ if (accepted.length === 0) {return;}
195
+ const next = [...filePreviewsRef.current, ...accepted];
196
+ filePreviewsRef.current = next;
197
+ setFilePreviews(next);
198
+ };
199
+
200
+ /** One picked image or video as the file the tray and the upload carry. */
201
+ const assetToFile = async (
202
+ asset: ImagePicker.ImagePickerAsset,
203
+ fallbackPrefix: string
204
+ ): Promise<MediaFile> => {
205
+ if (asset.type === 'video') {
206
+ return {
207
+ uri: asset.uri,
208
+ type: asset.mimeType || 'video/mp4',
209
+ name: asset.fileName || asset.uri.split('/').pop() || `${fallbackPrefix}_${Date.now()}.mp4`,
210
+ size: (asset as any).fileSize,
211
+ };
212
+ }
213
+ // Videos pass through unchanged; only images get the HEIC→JPEG
214
+ // normalization (manipulator is image-only).
215
+ const normalized = await normalizeImageAsset(asset);
216
+ return {
217
+ uri: normalized.uri,
218
+ type: normalized.mime,
219
+ name: normalized.name,
220
+ size: (asset as any).fileSize,
221
+ };
186
222
  };
187
223
 
188
224
  // Both pickers use expo-image-picker / expo-document-picker. They
@@ -205,33 +241,14 @@ const SendInput: React.FC<SendInputProps> = ({
205
241
  }
206
242
 
207
243
  try {
244
+ // Camera is a single shot per tap; the shot joins the tray.
208
245
  const result = await ImagePicker.launchCameraAsync({
209
246
  mediaTypes: ['images', 'videos'],
210
247
  quality: 0.9,
211
248
  videoQuality: ImagePicker.UIImagePickerControllerQualityType.Medium,
212
249
  });
213
250
  if (result.canceled || !result.assets?.[0]) {return;}
214
- const asset = result.assets[0];
215
- if (asset.type === 'video') {
216
- handleFileSelect([
217
- {
218
- uri: asset.uri,
219
- type: asset.mimeType || 'video/mp4',
220
- name: asset.fileName || asset.uri.split('/').pop() || `camera_${Date.now()}.mp4`,
221
- size: (asset as any).fileSize,
222
- },
223
- ]);
224
- return;
225
- }
226
- const normalized = await normalizeImageAsset(asset);
227
- handleFileSelect([
228
- {
229
- uri: normalized.uri,
230
- type: normalized.mime,
231
- name: normalized.name,
232
- size: (asset as any).fileSize,
233
- },
234
- ]);
251
+ handleFileSelect([await assetToFile(result.assets[0], 'camera')]);
235
252
  } catch (error) {
236
253
  console.error('Camera error:', error);
237
254
  }
@@ -244,38 +261,29 @@ const SendInput: React.FC<SendInputProps> = ({
244
261
  return;
245
262
  }
246
263
 
264
+ // The library takes as many as the tray has room for (at least one).
265
+ const remaining = mediaFileCap(config) - filePreviewsRef.current.length;
266
+ if (remaining <= 0) {
267
+ Alert.alert(t('attachment.limit', { count: mediaFileCap(config) }));
268
+ return;
269
+ }
270
+
247
271
  try {
248
272
  const result = await ImagePicker.launchImageLibraryAsync({
249
273
  mediaTypes: ['images', 'videos'],
250
274
  allowsEditing: false,
251
275
  quality: 0.9,
276
+ allowsMultipleSelection: true,
277
+ selectionLimit: remaining,
252
278
  preferredAssetRepresentationMode:
253
279
  ImagePicker.UIImagePickerPreferredAssetRepresentationMode.Compatible,
254
280
  });
255
- if (result.canceled || !result.assets?.[0]) {return;}
256
- const asset = result.assets[0];
257
- // Videos pass through unchanged; only images get the HEIC→JPEG
258
- // normalization (manipulator is image-only).
259
- if (asset.type === 'video') {
260
- handleFileSelect([
261
- {
262
- uri: asset.uri,
263
- type: asset.mimeType || 'video/mp4',
264
- name: asset.fileName || asset.uri.split('/').pop() || `gallery_${Date.now()}.mp4`,
265
- size: (asset as any).fileSize,
266
- },
267
- ]);
268
- return;
281
+ if (result.canceled || !result.assets?.length) {return;}
282
+ const picked: MediaFile[] = [];
283
+ for (const asset of result.assets) {
284
+ picked.push(await assetToFile(asset, 'gallery'));
269
285
  }
270
- const normalized = await normalizeImageAsset(asset);
271
- handleFileSelect([
272
- {
273
- uri: normalized.uri,
274
- type: normalized.mime,
275
- name: normalized.name,
276
- size: (asset as any).fileSize,
277
- },
278
- ]);
286
+ handleFileSelect(picked);
279
287
  } catch (error) {
280
288
  console.error('Gallery error:', error);
281
289
  }
@@ -336,19 +344,18 @@ const SendInput: React.FC<SendInputProps> = ({
336
344
  try {
337
345
  const result = await DocumentPicker.getDocumentAsync({
338
346
  type: '*/*',
339
- multiple: false,
347
+ multiple: true,
340
348
  copyToCacheDirectory: true,
341
349
  });
342
- if (result.canceled || !result.assets?.[0]) {return;}
343
- const asset = result.assets[0];
344
- handleFileSelect([
345
- {
350
+ if (result.canceled || !result.assets?.length) {return;}
351
+ handleFileSelect(
352
+ result.assets.map((asset) => ({
346
353
  uri: asset.uri,
347
354
  type: asset.mimeType || 'application/octet-stream',
348
355
  name: asset.name || `file_${Date.now()}`,
349
356
  size: asset.size ?? undefined,
350
- },
351
- ]);
357
+ }))
358
+ );
352
359
  } catch (err) {
353
360
  console.error('DocumentPicker error:', err);
354
361
  } finally {
@@ -383,6 +390,11 @@ const SendInput: React.FC<SendInputProps> = ({
383
390
  const libraryPermissionCheckedRef = useRef(false);
384
391
 
385
392
  const handleAttachPress = () => {
393
+ const cap = mediaFileCap(config);
394
+ if (filePreviewsRef.current.length >= cap) {
395
+ Alert.alert(t('attachment.limit', { count: cap }));
396
+ return;
397
+ }
386
398
  if (libraryPermissionCheckedRef.current) {
387
399
  setShowMediaMenu(true);
388
400
  return;
@@ -594,17 +606,19 @@ const SendInput: React.FC<SendInputProps> = ({
594
606
  setFilePreviews([]);
595
607
 
596
608
  try {
597
- for (const file of filesToSend) {
598
- try {
599
- await sendMedia(file, file.type);
600
- } catch (err) {
601
- console.error(err);
602
- return;
603
- }
604
- }
605
- if (messageToSend) {
609
+ if (filesToSend.length > 0) {
610
+ // The files go as ONE message. The text typed with them is its
611
+ // caption, never a separate text message.
612
+ await sendMedia(
613
+ filesToSend,
614
+ filesToSend[0]!.type,
615
+ messageToSend ? { text: messageToSend } : undefined
616
+ );
617
+ } else if (messageToSend) {
606
618
  sendMessage(messageToSend);
607
619
  }
620
+ } catch (err) {
621
+ console.error(err);
608
622
  } finally {
609
623
  sendingRef.current = false;
610
624
  }
@@ -29,6 +29,10 @@ interface CustomMessageVideoProps {
29
29
  mimetype: string;
30
30
  /** The backend-generated poster frame (message.locationPreview). */
31
31
  previewURL?: string;
32
+ /** Album mode: fill a tile of exactly this size (square corners; the tile clips). */
33
+ tile?: { width: number; height: number };
34
+ /** Replaces the default "open this one file" tap, e.g. to open an album at an index. */
35
+ onOpen?: () => void;
32
36
  }
33
37
 
34
38
  const PlayBadge: React.FC = () => (
@@ -51,11 +55,13 @@ const PosterVideo: React.FC<{
51
55
  previewURL: string;
52
56
  onOpen: () => void;
53
57
  onPosterError: () => void;
54
- }> = ({ previewURL, onOpen, onPosterError }) => {
55
- const [dims, setDims] = useState<MediaDims>(defaultMediaDims());
58
+ tile?: { width: number; height: number };
59
+ }> = ({ previewURL, onOpen, onPosterError, tile }) => {
60
+ const [measured, setDims] = useState<MediaDims>(defaultMediaDims());
61
+ const dims: MediaDims = tile ?? measured;
56
62
 
57
63
  useEffect(() => {
58
- if (!previewURL) {
64
+ if (!previewURL || tile) {
59
65
  return;
60
66
  }
61
67
  let active = true;
@@ -73,12 +79,16 @@ const PosterVideo: React.FC<{
73
79
  return () => {
74
80
  active = false;
75
81
  };
76
- }, [previewURL]);
82
+ }, [previewURL, tile]);
77
83
 
78
84
  return (
79
85
  <Pressable
80
86
  onPress={onOpen}
81
- style={[styles.wrapper, { width: dims.width, height: dims.height }]}
87
+ style={[
88
+ styles.wrapper,
89
+ { width: dims.width, height: dims.height },
90
+ tile && styles.tileWrapper,
91
+ ]}
82
92
  accessibilityRole="button"
83
93
  accessibilityLabel="Play video"
84
94
  >
@@ -115,13 +125,18 @@ const PosterVideo: React.FC<{
115
125
  */
116
126
  const PlaceholderVideo: React.FC<{
117
127
  onOpen: () => void;
118
- }> = ({ onOpen }) => {
119
- const dims = defaultMediaDims();
128
+ tile?: { width: number; height: number };
129
+ }> = ({ onOpen, tile }) => {
130
+ const dims = tile ?? defaultMediaDims();
120
131
 
121
132
  return (
122
133
  <Pressable
123
134
  onPress={onOpen}
124
- style={[styles.wrapper, { width: dims.width, height: dims.height }]}
135
+ style={[
136
+ styles.wrapper,
137
+ { width: dims.width, height: dims.height },
138
+ tile && styles.tileWrapper,
139
+ ]}
125
140
  accessibilityRole="button"
126
141
  accessibilityLabel="Play video"
127
142
  >
@@ -136,11 +151,17 @@ const CustomMessageVideo: React.FC<CustomMessageVideoProps> = ({
136
151
  fileURL,
137
152
  mimetype,
138
153
  previewURL,
154
+ tile,
155
+ onOpen,
139
156
  }) => {
140
157
  const dispatch = useDispatch();
141
158
  const [posterFailed, setPosterFailed] = useState(false);
142
159
 
143
160
  const handleOpen = () => {
161
+ if (onOpen) {
162
+ onOpen();
163
+ return;
164
+ }
144
165
  dispatch(setActiveFile({ fileName, fileURL, mimetype, originalName }));
145
166
  dispatch(setActiveModal(MODAL_TYPES.FILE_PREVIEW));
146
167
  };
@@ -150,6 +171,7 @@ const CustomMessageVideo: React.FC<CustomMessageVideoProps> = ({
150
171
  <PosterVideo
151
172
  previewURL={previewURL}
152
173
  onOpen={handleOpen}
174
+ tile={tile}
153
175
  onPosterError={() => {
154
176
  if (isSecureFileUrl(previewURL)) {
155
177
  requestFileTokenRecovery();
@@ -160,7 +182,7 @@ const CustomMessageVideo: React.FC<CustomMessageVideoProps> = ({
160
182
  );
161
183
  }
162
184
 
163
- return <PlaceholderVideo onOpen={handleOpen} />;
185
+ return <PlaceholderVideo onOpen={handleOpen} tile={tile} />;
164
186
  };
165
187
 
166
188
  export default CustomMessageVideo;
@@ -171,6 +193,9 @@ const styles = StyleSheet.create({
171
193
  overflow: 'hidden',
172
194
  backgroundColor: '#000',
173
195
  },
196
+ tileWrapper: {
197
+ borderRadius: 0,
198
+ },
174
199
  video: {
175
200
  width: '100%',
176
201
  height: '100%',
@@ -63,6 +63,7 @@ import { updateMessagesTillLast } from '../helpers/updateMessagesTillLast';
63
63
  import { secureUserStorage } from '../helpers/secureUserStorage';
64
64
  import { clearPersistedState } from '../roomStore/persistence';
65
65
  import { pushLog as devPushLog } from '../utils/devLogger';
66
+ import { computeReadWatermark } from '../helpers/readReceipts';
66
67
 
67
68
  interface InitMode {
68
69
  current: 'provider' | 'chat';
@@ -794,6 +795,26 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
794
795
  let lastMessagesRef: any = null;
795
796
  let lastStampedMs = 0;
796
797
  let flushTimer: ReturnType<typeof setTimeout> | null = null;
798
+ // Room whose XEP-0333 marker is waiting on the 2 s debounce below. Set
799
+ // only while that room is the one on screen.
800
+ let pendingMarkerJid: string | null = null;
801
+
802
+ // Sends the waiting marker now: the debounce is cut short because the
803
+ // user left the room or the app, so it would otherwise be lost. The room
804
+ // counts as on screen for this one call (it was when the marker was
805
+ // queued); the jump window, the scroll boundary and the disable flags
806
+ // still apply inside computeReadWatermark.
807
+ const flushPendingMarker = () => {
808
+ const jid = pendingMarkerJid;
809
+ pendingMarkerJid = null;
810
+ if (!jid) {return;}
811
+ const st = store.getState();
812
+ const watermark = computeReadWatermark(
813
+ { ...st, rooms: { ...st.rooms, visibleRoomJID: jid } } as any,
814
+ jid
815
+ );
816
+ if (watermark) {client?.sendDisplayedMarkerStanza?.(watermark, jid);}
817
+ };
797
818
 
798
819
  const advance = () => {
799
820
  const s = store.getState();
@@ -826,9 +847,13 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
826
847
  setLastViewedTimestamp({ chatJID: jid, timestamp: newest })
827
848
  );
828
849
 
850
+ // Another room took over the debounce: send the old room's marker first.
851
+ if (pendingMarkerJid && pendingMarkerJid !== jid) {flushPendingMarker();}
829
852
  if (flushTimer) {clearTimeout(flushTimer);}
830
853
  if (client?.flushLastViewedToPrivateStoreStanza) {
854
+ if (s.rooms?.visibleRoomJID === jid) {pendingMarkerJid = jid;}
831
855
  flushTimer = setTimeout(() => {
856
+ pendingMarkerJid = null;
832
857
  // Re-read fresh state rather than closing over `boundaryTs`:
833
858
  // the user may have scrolled back to the bottom (clearing the
834
859
  // boundary) in the 2s between the stamp and this flush, and
@@ -845,6 +870,12 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
845
870
  ),
846
871
  })
847
872
  .catch(() => {});
873
+ // XEP-0333 read marker, on the same debounce as the private-store
874
+ // write above. computeReadWatermark sends nothing unless the room is
875
+ // still on screen, no jump window is open and the newest message
876
+ // is someone else's.
877
+ const watermark = computeReadWatermark(freshState, jid);
878
+ if (watermark) {client.sendDisplayedMarkerStanza?.(watermark, jid);}
848
879
  }, 2000);
849
880
  }
850
881
  };
@@ -854,6 +885,7 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
854
885
  return () => {
855
886
  unsub();
856
887
  if (flushTimer) {clearTimeout(flushTimer);}
888
+ flushPendingMarker();
857
889
  };
858
890
  }, [isVisible, client]);
859
891
 
@@ -12,7 +12,7 @@ import {
12
12
  type Progress,
13
13
  } from './fileCipher';
14
14
 
15
- export { parseSealedMediaBody } from './sealedBody';
15
+ export { parseSealedMediaBody, parseSealedMediaPayload } from './sealedBody';
16
16
 
17
17
  const MAGIC = Uint8Array.from([0x45, 0x54, 0x48, 0x4f, 0x46, 0x49, 0x4c, 0x45]);
18
18
  const VERSION = 1;
@@ -1,17 +1,56 @@
1
- export function parseSealedMediaBody(body?: string): string[] | undefined {
1
+ import type { IMentionSpan } from '../types/models/message.model';
2
+ import { normalizeCaptionInput } from '../helpers/caption';
3
+
4
+ /** What a sealed media <body> carries besides the keys. */
5
+ export interface SealedMediaPayload {
6
+ keys: string[];
7
+ /** The text typed with the files. Sealed here, never on <data>. */
8
+ caption?: string;
9
+ captionMentions?: IMentionSpan[];
10
+ }
11
+
12
+ /**
13
+ * Pulls the attachment keys (and the caption, when there is one) out of a
14
+ * decrypted media <body>: `{v:1,keys:[...],caption?,captionMentions?}`, which
15
+ * the sender writes in place of the usual literal "media". Undefined for
16
+ * anything else, so an ordinary media message falls through untouched.
17
+ */
18
+ export function parseSealedMediaPayload(
19
+ body?: string
20
+ ): SealedMediaPayload | undefined {
2
21
  const text = String(body || '').trim();
3
22
  if (!text.startsWith('{')) {return undefined;}
4
23
 
5
24
  try {
6
- const parsed = JSON.parse(text) as { v?: unknown; keys?: unknown };
25
+ const parsed = JSON.parse(text) as {
26
+ v?: unknown;
27
+ keys?: unknown;
28
+ caption?: unknown;
29
+ captionMentions?: unknown;
30
+ };
7
31
  if (parsed?.v !== 1 || !Array.isArray(parsed.keys)) {return undefined;}
8
32
  const keys = parsed.keys.filter(
9
33
  (k): k is string => typeof k === 'string' && k.length > 0
10
34
  );
11
- return keys.length === parsed.keys.length && keys.length > 0
12
- ? keys
13
- : undefined;
35
+ if (!(keys.length === parsed.keys.length && keys.length > 0)) {
36
+ return undefined;
37
+ }
38
+
39
+ const caption = normalizeCaptionInput({
40
+ text: typeof parsed.caption === 'string' ? parsed.caption : '',
41
+ mentions: parsed.captionMentions as IMentionSpan[] | undefined,
42
+ });
43
+ return {
44
+ keys,
45
+ ...(caption ? { caption: caption.caption } : {}),
46
+ ...(caption?.mentions ? { captionMentions: caption.mentions } : {}),
47
+ };
14
48
  } catch {
15
49
  return undefined;
16
50
  }
17
51
  }
52
+
53
+ /** The attachment keys out of a sealed media <body>; see parseSealedMediaPayload. */
54
+ export function parseSealedMediaBody(body?: string): string[] | undefined {
55
+ return parseSealedMediaPayload(body)?.keys;
56
+ }