@ethora/chat-component-rn 26.8.6 → 26.8.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 (409) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/README.md +40 -0
  3. package/lib/commonjs/assets/icons.js +4 -2
  4. package/lib/commonjs/assets/icons.js.map +1 -1
  5. package/lib/commonjs/components/MainComponents/ChatHeader.js +28 -6
  6. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  7. package/lib/commonjs/components/MainComponents/ChatRoom.js +9 -1
  8. package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
  9. package/lib/commonjs/components/MainComponents/EncryptedSendNotice.js +47 -0
  10. package/lib/commonjs/components/MainComponents/EncryptedSendNotice.js.map +1 -0
  11. package/lib/commonjs/components/MainComponents/EncryptionCard.js +286 -0
  12. package/lib/commonjs/components/MainComponents/EncryptionCard.js.map +1 -0
  13. package/lib/commonjs/components/MainComponents/MediaMessage.js +7 -0
  14. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  15. package/lib/commonjs/components/MainComponents/ReduxWrapper.js +5 -0
  16. package/lib/commonjs/components/MainComponents/ReduxWrapper.js.map +1 -1
  17. package/lib/commonjs/components/MainComponents/SealedAttachment.js +231 -0
  18. package/lib/commonjs/components/MainComponents/SealedAttachment.js.map +1 -0
  19. package/lib/commonjs/components/MessageBubble/BottomReplyContainer.js +2 -1
  20. package/lib/commonjs/components/MessageBubble/BottomReplyContainer.js.map +1 -1
  21. package/lib/commonjs/components/MessageBubble/Message.js +66 -6
  22. package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
  23. package/lib/commonjs/components/MessageBubble/MessageInteractions.js +2 -1
  24. package/lib/commonjs/components/MessageBubble/MessageInteractions.js.map +1 -1
  25. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +7 -4
  26. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  27. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +159 -16
  28. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  29. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js +4 -0
  30. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  31. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +29 -6
  32. package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  33. package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js +5 -1
  34. package/lib/commonjs/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
  35. package/lib/commonjs/components/RoomComponents/ChatRoomItem.js +32 -9
  36. package/lib/commonjs/components/RoomComponents/ChatRoomItem.js.map +1 -1
  37. package/lib/commonjs/components/RoomComponents/LastMessageItem.js +6 -0
  38. package/lib/commonjs/components/RoomComponents/LastMessageItem.js.map +1 -1
  39. package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js +2 -2
  40. package/lib/commonjs/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  41. package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js +4 -6
  42. package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  43. package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js +2 -2
  44. package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  45. package/lib/commonjs/components/RoomComponents/styled/LastMessageSealed.js +41 -0
  46. package/lib/commonjs/components/RoomComponents/styled/LastMessageSealed.js.map +1 -0
  47. package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js +2 -2
  48. package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  49. package/lib/commonjs/components/Thread/ThreadWrapper.js +53 -45
  50. package/lib/commonjs/components/Thread/ThreadWrapper.js.map +1 -1
  51. package/lib/commonjs/components/UsersList/UsersList.js +2 -1
  52. package/lib/commonjs/components/UsersList/UsersList.js.map +1 -1
  53. package/lib/commonjs/components/styled/SendInput.js +4 -2
  54. package/lib/commonjs/components/styled/SendInput.js.map +1 -1
  55. package/lib/commonjs/e2ee/crypto.js +207 -0
  56. package/lib/commonjs/e2ee/crypto.js.map +1 -0
  57. package/lib/commonjs/e2ee/deviceStore.js +84 -0
  58. package/lib/commonjs/e2ee/deviceStore.js.map +1 -0
  59. package/lib/commonjs/e2ee/fileCipher.js +95 -0
  60. package/lib/commonjs/e2ee/fileCipher.js.map +1 -0
  61. package/lib/commonjs/e2ee/fileEnvelope.js +79 -0
  62. package/lib/commonjs/e2ee/fileEnvelope.js.map +1 -0
  63. package/lib/commonjs/e2ee/index.js +210 -0
  64. package/lib/commonjs/e2ee/index.js.map +1 -0
  65. package/lib/commonjs/e2ee/limits.js +9 -0
  66. package/lib/commonjs/e2ee/limits.js.map +1 -0
  67. package/lib/commonjs/e2ee/ns.js +8 -0
  68. package/lib/commonjs/e2ee/ns.js.map +1 -0
  69. package/lib/commonjs/e2ee/omemo.js +622 -0
  70. package/lib/commonjs/e2ee/omemo.js.map +1 -0
  71. package/lib/commonjs/e2ee/protobuf.js +149 -0
  72. package/lib/commonjs/e2ee/protobuf.js.map +1 -0
  73. package/lib/commonjs/e2ee/ratchet.js +238 -0
  74. package/lib/commonjs/e2ee/ratchet.js.map +1 -0
  75. package/lib/commonjs/e2ee/sealedBody.js +23 -0
  76. package/lib/commonjs/e2ee/sealedBody.js.map +1 -0
  77. package/lib/commonjs/e2ee/sealedFiles.js +174 -0
  78. package/lib/commonjs/e2ee/sealedFiles.js.map +1 -0
  79. package/lib/commonjs/e2ee/send.js +54 -0
  80. package/lib/commonjs/e2ee/send.js.map +1 -0
  81. package/lib/commonjs/e2ee/stanza.js +114 -0
  82. package/lib/commonjs/e2ee/stanza.js.map +1 -0
  83. package/lib/commonjs/e2ee/store.js +43 -0
  84. package/lib/commonjs/e2ee/store.js.map +1 -0
  85. package/lib/commonjs/helpers/accountName.js +23 -0
  86. package/lib/commonjs/helpers/accountName.js.map +1 -0
  87. package/lib/commonjs/helpers/createMessageFromXml.js +19 -4
  88. package/lib/commonjs/helpers/createMessageFromXml.js.map +1 -1
  89. package/lib/commonjs/helpers/createUserNameFromSetUser.js +2 -1
  90. package/lib/commonjs/helpers/createUserNameFromSetUser.js.map +1 -1
  91. package/lib/commonjs/helpers/displayName.js +16 -0
  92. package/lib/commonjs/helpers/displayName.js.map +1 -0
  93. package/lib/commonjs/helpers/persistCrypto.js +5 -0
  94. package/lib/commonjs/helpers/persistCrypto.js.map +1 -1
  95. package/lib/commonjs/helpers/secureKeyValue.js +20 -0
  96. package/lib/commonjs/helpers/secureKeyValue.js.map +1 -1
  97. package/lib/commonjs/helpers/videoCache.js +136 -0
  98. package/lib/commonjs/helpers/videoCache.js.map +1 -0
  99. package/lib/commonjs/hooks/useLogout.js +8 -0
  100. package/lib/commonjs/hooks/useLogout.js.map +1 -1
  101. package/lib/commonjs/hooks/useSendMessage.js +71 -7
  102. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  103. package/lib/commonjs/i18n/strings.js +180 -0
  104. package/lib/commonjs/i18n/strings.js.map +1 -1
  105. package/lib/commonjs/main.js +13 -0
  106. package/lib/commonjs/main.js.map +1 -1
  107. package/lib/commonjs/networking/api-requests/auth.api.js +4 -1
  108. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  109. package/lib/commonjs/networking/api-requests/rooms.api.js +111 -17
  110. package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
  111. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js +40 -0
  112. package/lib/commonjs/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  113. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js +16 -3
  114. package/lib/commonjs/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  115. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js +13 -6
  116. package/lib/commonjs/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  117. package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +14 -6
  118. package/lib/commonjs/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
  119. package/lib/commonjs/networking/xmppClient.js +6 -0
  120. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  121. package/lib/commonjs/roomStore/persistBackend.js +9 -12
  122. package/lib/commonjs/roomStore/persistBackend.js.map +1 -1
  123. package/lib/commonjs/roomStore/persistence.js +8 -1
  124. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  125. package/lib/commonjs/version.js +1 -1
  126. package/lib/module/assets/icons.js +3 -1
  127. package/lib/module/assets/icons.js.map +1 -1
  128. package/lib/module/components/MainComponents/ChatHeader.js +29 -7
  129. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  130. package/lib/module/components/MainComponents/ChatRoom.js +9 -1
  131. package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
  132. package/lib/module/components/MainComponents/EncryptedSendNotice.js +42 -0
  133. package/lib/module/components/MainComponents/EncryptedSendNotice.js.map +1 -0
  134. package/lib/module/components/MainComponents/EncryptionCard.js +281 -0
  135. package/lib/module/components/MainComponents/EncryptionCard.js.map +1 -0
  136. package/lib/module/components/MainComponents/MediaMessage.js +7 -0
  137. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  138. package/lib/module/components/MainComponents/ReduxWrapper.js +5 -0
  139. package/lib/module/components/MainComponents/ReduxWrapper.js.map +1 -1
  140. package/lib/module/components/MainComponents/SealedAttachment.js +224 -0
  141. package/lib/module/components/MainComponents/SealedAttachment.js.map +1 -0
  142. package/lib/module/components/MessageBubble/BottomReplyContainer.js +2 -1
  143. package/lib/module/components/MessageBubble/BottomReplyContainer.js.map +1 -1
  144. package/lib/module/components/MessageBubble/Message.js +69 -9
  145. package/lib/module/components/MessageBubble/Message.js.map +1 -1
  146. package/lib/module/components/MessageBubble/MessageInteractions.js +2 -1
  147. package/lib/module/components/MessageBubble/MessageInteractions.js.map +1 -1
  148. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +7 -4
  149. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  150. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +161 -18
  151. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  152. package/lib/module/components/Modals/FilePreviewModal/PdfView.js +4 -0
  153. package/lib/module/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  154. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +32 -8
  155. package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
  156. package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js +4 -0
  157. package/lib/module/components/Modals/UserSettingsModal/UserSettingsModal.js.map +1 -1
  158. package/lib/module/components/RoomComponents/ChatRoomItem.js +32 -9
  159. package/lib/module/components/RoomComponents/ChatRoomItem.js.map +1 -1
  160. package/lib/module/components/RoomComponents/LastMessageItem.js +6 -0
  161. package/lib/module/components/RoomComponents/LastMessageItem.js.map +1 -1
  162. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js +2 -2
  163. package/lib/module/components/RoomComponents/styled/LastAudioMessage.js.map +1 -1
  164. package/lib/module/components/RoomComponents/styled/LastMessageFile.js +4 -6
  165. package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  166. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +2 -2
  167. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  168. package/lib/module/components/RoomComponents/styled/LastMessageSealed.js +36 -0
  169. package/lib/module/components/RoomComponents/styled/LastMessageSealed.js.map +1 -0
  170. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +2 -2
  171. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  172. package/lib/module/components/Thread/ThreadWrapper.js +54 -46
  173. package/lib/module/components/Thread/ThreadWrapper.js.map +1 -1
  174. package/lib/module/components/UsersList/UsersList.js +2 -1
  175. package/lib/module/components/UsersList/UsersList.js.map +1 -1
  176. package/lib/module/components/styled/SendInput.js +4 -2
  177. package/lib/module/components/styled/SendInput.js.map +1 -1
  178. package/lib/module/e2ee/crypto.js +170 -0
  179. package/lib/module/e2ee/crypto.js.map +1 -0
  180. package/lib/module/e2ee/deviceStore.js +77 -0
  181. package/lib/module/e2ee/deviceStore.js.map +1 -0
  182. package/lib/module/e2ee/fileCipher.js +90 -0
  183. package/lib/module/e2ee/fileCipher.js.map +1 -0
  184. package/lib/module/e2ee/fileEnvelope.js +66 -0
  185. package/lib/module/e2ee/fileEnvelope.js.map +1 -0
  186. package/lib/module/e2ee/index.js +185 -0
  187. package/lib/module/e2ee/index.js.map +1 -0
  188. package/lib/module/e2ee/limits.js +5 -0
  189. package/lib/module/e2ee/limits.js.map +1 -0
  190. package/lib/module/e2ee/ns.js +4 -0
  191. package/lib/module/e2ee/ns.js.map +1 -0
  192. package/lib/module/e2ee/omemo.js +610 -0
  193. package/lib/module/e2ee/omemo.js.map +1 -0
  194. package/lib/module/e2ee/protobuf.js +140 -0
  195. package/lib/module/e2ee/protobuf.js.map +1 -0
  196. package/lib/module/e2ee/ratchet.js +228 -0
  197. package/lib/module/e2ee/ratchet.js.map +1 -0
  198. package/lib/module/e2ee/sealedBody.js +19 -0
  199. package/lib/module/e2ee/sealedBody.js.map +1 -0
  200. package/lib/module/e2ee/sealedFiles.js +154 -0
  201. package/lib/module/e2ee/sealedFiles.js.map +1 -0
  202. package/lib/module/e2ee/send.js +48 -0
  203. package/lib/module/e2ee/send.js.map +1 -0
  204. package/lib/module/e2ee/stanza.js +107 -0
  205. package/lib/module/e2ee/stanza.js.map +1 -0
  206. package/lib/module/e2ee/store.js +35 -0
  207. package/lib/module/e2ee/store.js.map +1 -0
  208. package/lib/module/helpers/accountName.js +18 -0
  209. package/lib/module/helpers/accountName.js.map +1 -0
  210. package/lib/module/helpers/createMessageFromXml.js +19 -4
  211. package/lib/module/helpers/createMessageFromXml.js.map +1 -1
  212. package/lib/module/helpers/createUserNameFromSetUser.js +3 -1
  213. package/lib/module/helpers/createUserNameFromSetUser.js.map +1 -1
  214. package/lib/module/helpers/displayName.js +11 -0
  215. package/lib/module/helpers/displayName.js.map +1 -0
  216. package/lib/module/helpers/persistCrypto.js +4 -0
  217. package/lib/module/helpers/persistCrypto.js.map +1 -1
  218. package/lib/module/helpers/secureKeyValue.js +19 -0
  219. package/lib/module/helpers/secureKeyValue.js.map +1 -1
  220. package/lib/module/helpers/videoCache.js +125 -0
  221. package/lib/module/helpers/videoCache.js.map +1 -0
  222. package/lib/module/hooks/useLogout.js +8 -0
  223. package/lib/module/hooks/useLogout.js.map +1 -1
  224. package/lib/module/hooks/useSendMessage.js +71 -7
  225. package/lib/module/hooks/useSendMessage.js.map +1 -1
  226. package/lib/module/i18n/strings.js +180 -0
  227. package/lib/module/i18n/strings.js.map +1 -1
  228. package/lib/module/main.js +1 -0
  229. package/lib/module/main.js.map +1 -1
  230. package/lib/module/networking/api-requests/auth.api.js +4 -1
  231. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  232. package/lib/module/networking/api-requests/rooms.api.js +109 -18
  233. package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
  234. package/lib/module/networking/xmpp/handleStanzas.xmpp.js +38 -0
  235. package/lib/module/networking/xmpp/handleStanzas.xmpp.js.map +1 -1
  236. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js +16 -3
  237. package/lib/module/networking/xmpp/sendMediaMessage.xmpp.js.map +1 -1
  238. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js +13 -6
  239. package/lib/module/networking/xmpp/sendTextMessage.xmpp.js.map +1 -1
  240. package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js +14 -6
  241. package/lib/module/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.js.map +1 -1
  242. package/lib/module/networking/xmppClient.js +6 -0
  243. package/lib/module/networking/xmppClient.js.map +1 -1
  244. package/lib/module/roomStore/persistBackend.js +10 -13
  245. package/lib/module/roomStore/persistBackend.js.map +1 -1
  246. package/lib/module/roomStore/persistence.js +8 -1
  247. package/lib/module/roomStore/persistence.js.map +1 -1
  248. package/lib/module/version.js +1 -1
  249. package/lib/typescript/assets/icons.d.ts +8 -0
  250. package/lib/typescript/assets/icons.d.ts.map +1 -1
  251. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  252. package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
  253. package/lib/typescript/components/MainComponents/EncryptedSendNotice.d.ts +4 -0
  254. package/lib/typescript/components/MainComponents/EncryptedSendNotice.d.ts.map +1 -0
  255. package/lib/typescript/components/MainComponents/EncryptionCard.d.ts +10 -0
  256. package/lib/typescript/components/MainComponents/EncryptionCard.d.ts.map +1 -0
  257. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  258. package/lib/typescript/components/MainComponents/ReduxWrapper.d.ts.map +1 -1
  259. package/lib/typescript/components/MainComponents/SealedAttachment.d.ts +13 -0
  260. package/lib/typescript/components/MainComponents/SealedAttachment.d.ts.map +1 -0
  261. package/lib/typescript/components/MessageBubble/BottomReplyContainer.d.ts.map +1 -1
  262. package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
  263. package/lib/typescript/components/MessageBubble/MessageInteractions.d.ts.map +1 -1
  264. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  265. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  266. package/lib/typescript/components/Modals/FilePreviewModal/PdfView.d.ts.map +1 -1
  267. package/lib/typescript/components/Modals/UserProfileModal/UserProfileModal.d.ts.map +1 -1
  268. package/lib/typescript/components/Modals/UserSettingsModal/UserSettingsModal.d.ts.map +1 -1
  269. package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts.map +1 -1
  270. package/lib/typescript/components/RoomComponents/LastMessageItem.d.ts.map +1 -1
  271. package/lib/typescript/components/RoomComponents/styled/LastAudioMessage.d.ts.map +1 -1
  272. package/lib/typescript/components/RoomComponents/styled/LastMessageSealed.d.ts +7 -0
  273. package/lib/typescript/components/RoomComponents/styled/LastMessageSealed.d.ts.map +1 -0
  274. package/lib/typescript/components/Thread/ThreadWrapper.d.ts.map +1 -1
  275. package/lib/typescript/components/UsersList/UsersList.d.ts.map +1 -1
  276. package/lib/typescript/components/styled/SendInput.d.ts +1 -0
  277. package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
  278. package/lib/typescript/e2ee/crypto.d.ts +28 -0
  279. package/lib/typescript/e2ee/crypto.d.ts.map +1 -0
  280. package/lib/typescript/e2ee/deviceStore.d.ts +4 -0
  281. package/lib/typescript/e2ee/deviceStore.d.ts.map +1 -0
  282. package/lib/typescript/e2ee/fileCipher.d.ts +7 -0
  283. package/lib/typescript/e2ee/fileCipher.d.ts.map +1 -0
  284. package/lib/typescript/e2ee/fileEnvelope.d.ts +27 -0
  285. package/lib/typescript/e2ee/fileEnvelope.d.ts.map +1 -0
  286. package/lib/typescript/e2ee/index.d.ts +23 -0
  287. package/lib/typescript/e2ee/index.d.ts.map +1 -0
  288. package/lib/typescript/e2ee/limits.d.ts +3 -0
  289. package/lib/typescript/e2ee/limits.d.ts.map +1 -0
  290. package/lib/typescript/e2ee/ns.d.ts +2 -0
  291. package/lib/typescript/e2ee/ns.d.ts.map +1 -0
  292. package/lib/typescript/e2ee/omemo.d.ts +64 -0
  293. package/lib/typescript/e2ee/omemo.d.ts.map +1 -0
  294. package/lib/typescript/e2ee/protobuf.d.ts +25 -0
  295. package/lib/typescript/e2ee/protobuf.d.ts.map +1 -0
  296. package/lib/typescript/e2ee/ratchet.d.ts +39 -0
  297. package/lib/typescript/e2ee/ratchet.d.ts.map +1 -0
  298. package/lib/typescript/e2ee/sealedBody.d.ts +2 -0
  299. package/lib/typescript/e2ee/sealedBody.d.ts.map +1 -0
  300. package/lib/typescript/e2ee/sealedFiles.d.ts +31 -0
  301. package/lib/typescript/e2ee/sealedFiles.d.ts.map +1 -0
  302. package/lib/typescript/e2ee/send.d.ts +6 -0
  303. package/lib/typescript/e2ee/send.d.ts.map +1 -0
  304. package/lib/typescript/e2ee/stanza.d.ts +8 -0
  305. package/lib/typescript/e2ee/stanza.d.ts.map +1 -0
  306. package/lib/typescript/e2ee/store.d.ts +16 -0
  307. package/lib/typescript/e2ee/store.d.ts.map +1 -0
  308. package/lib/typescript/helpers/accountName.d.ts +7 -0
  309. package/lib/typescript/helpers/accountName.d.ts.map +1 -0
  310. package/lib/typescript/helpers/createMessageFromXml.d.ts.map +1 -1
  311. package/lib/typescript/helpers/createUserNameFromSetUser.d.ts.map +1 -1
  312. package/lib/typescript/helpers/displayName.d.ts +2 -0
  313. package/lib/typescript/helpers/displayName.d.ts.map +1 -0
  314. package/lib/typescript/helpers/persistCrypto.d.ts +1 -0
  315. package/lib/typescript/helpers/persistCrypto.d.ts.map +1 -1
  316. package/lib/typescript/helpers/secureKeyValue.d.ts +4 -0
  317. package/lib/typescript/helpers/secureKeyValue.d.ts.map +1 -1
  318. package/lib/typescript/helpers/videoCache.d.ts +19 -0
  319. package/lib/typescript/helpers/videoCache.d.ts.map +1 -0
  320. package/lib/typescript/hooks/useLogout.d.ts.map +1 -1
  321. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  322. package/lib/typescript/i18n/strings.d.ts.map +1 -1
  323. package/lib/typescript/main.d.ts +2 -0
  324. package/lib/typescript/main.d.ts.map +1 -1
  325. package/lib/typescript/networking/api-requests/auth.api.d.ts +4 -1
  326. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  327. package/lib/typescript/networking/api-requests/rooms.api.d.ts +22 -1
  328. package/lib/typescript/networking/api-requests/rooms.api.d.ts.map +1 -1
  329. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts +1 -0
  330. package/lib/typescript/networking/xmpp/handleStanzas.xmpp.d.ts.map +1 -1
  331. package/lib/typescript/networking/xmpp/sendMediaMessage.xmpp.d.ts.map +1 -1
  332. package/lib/typescript/networking/xmpp/sendTextMessage.xmpp.d.ts.map +1 -1
  333. package/lib/typescript/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.d.ts.map +1 -1
  334. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  335. package/lib/typescript/roomStore/persistBackend.d.ts.map +1 -1
  336. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  337. package/lib/typescript/types/models/message.model.d.ts +5 -0
  338. package/lib/typescript/types/models/message.model.d.ts.map +1 -1
  339. package/lib/typescript/types/models/room.model.d.ts +5 -0
  340. package/lib/typescript/types/models/room.model.d.ts.map +1 -1
  341. package/lib/typescript/types/types.d.ts +4 -0
  342. package/lib/typescript/types/types.d.ts.map +1 -1
  343. package/lib/typescript/version.d.ts +1 -1
  344. package/package.json +8 -4
  345. package/src/assets/icons.tsx +27 -0
  346. package/src/components/MainComponents/ChatHeader.tsx +30 -9
  347. package/src/components/MainComponents/ChatRoom.tsx +11 -1
  348. package/src/components/MainComponents/EncryptedSendNotice.tsx +35 -0
  349. package/src/components/MainComponents/EncryptionCard.tsx +287 -0
  350. package/src/components/MainComponents/MediaMessage.tsx +5 -0
  351. package/src/components/MainComponents/ReduxWrapper.tsx +5 -0
  352. package/src/components/MainComponents/SealedAttachment.tsx +251 -0
  353. package/src/components/MessageBubble/BottomReplyContainer.tsx +2 -1
  354. package/src/components/MessageBubble/Message.tsx +96 -6
  355. package/src/components/MessageBubble/MessageInteractions.tsx +2 -3
  356. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +7 -4
  357. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +181 -35
  358. package/src/components/Modals/FilePreviewModal/PdfView.tsx +4 -0
  359. package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +53 -8
  360. package/src/components/Modals/UserSettingsModal/UserSettingsModal.tsx +5 -0
  361. package/src/components/RoomComponents/ChatRoomItem.tsx +23 -11
  362. package/src/components/RoomComponents/LastMessageItem.tsx +5 -0
  363. package/src/components/RoomComponents/styled/LastAudioMessage.tsx +3 -1
  364. package/src/components/RoomComponents/styled/LastMessageFile.tsx +2 -2
  365. package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +1 -1
  366. package/src/components/RoomComponents/styled/LastMessageSealed.tsx +31 -0
  367. package/src/components/RoomComponents/styled/LastMessageVideo.tsx +1 -1
  368. package/src/components/Thread/ThreadWrapper.tsx +13 -0
  369. package/src/components/UsersList/UsersList.tsx +2 -1
  370. package/src/components/styled/SendInput.tsx +5 -2
  371. package/src/e2ee/crypto.ts +194 -0
  372. package/src/e2ee/deviceStore.ts +80 -0
  373. package/src/e2ee/fileCipher.ts +86 -0
  374. package/src/e2ee/fileEnvelope.ts +115 -0
  375. package/src/e2ee/index.ts +192 -0
  376. package/src/e2ee/limits.ts +2 -0
  377. package/src/e2ee/ns.ts +1 -0
  378. package/src/e2ee/omemo.ts +802 -0
  379. package/src/e2ee/protobuf.ts +188 -0
  380. package/src/e2ee/ratchet.ts +271 -0
  381. package/src/e2ee/sealedBody.ts +17 -0
  382. package/src/e2ee/sealedFiles.ts +181 -0
  383. package/src/e2ee/send.ts +70 -0
  384. package/src/e2ee/stanza.ts +133 -0
  385. package/src/e2ee/store.ts +54 -0
  386. package/src/helpers/accountName.ts +15 -0
  387. package/src/helpers/createMessageFromXml.ts +31 -6
  388. package/src/helpers/createUserNameFromSetUser.ts +2 -1
  389. package/src/helpers/displayName.ts +9 -0
  390. package/src/helpers/persistCrypto.ts +5 -0
  391. package/src/helpers/secureKeyValue.ts +15 -0
  392. package/src/helpers/videoCache.ts +138 -0
  393. package/src/hooks/useLogout.tsx +9 -0
  394. package/src/hooks/useSendMessage.tsx +71 -5
  395. package/src/i18n/strings.ts +180 -0
  396. package/src/main.ts +2 -0
  397. package/src/networking/api-requests/auth.api.ts +9 -1
  398. package/src/networking/api-requests/rooms.api.ts +121 -18
  399. package/src/networking/xmpp/handleStanzas.xmpp.ts +49 -0
  400. package/src/networking/xmpp/sendMediaMessage.xmpp.ts +19 -1
  401. package/src/networking/xmpp/sendTextMessage.xmpp.ts +17 -10
  402. package/src/networking/xmpp/sendTextMessageWithTranslateTag.xmpp.ts +23 -10
  403. package/src/networking/xmppClient.ts +6 -0
  404. package/src/roomStore/persistBackend.ts +8 -11
  405. package/src/roomStore/persistence.ts +15 -0
  406. package/src/types/models/message.model.ts +5 -0
  407. package/src/types/models/room.model.ts +5 -0
  408. package/src/types/types.ts +4 -0
  409. package/src/version.ts +1 -1
@@ -0,0 +1,802 @@
1
+ import { xml, type Client } from '@xmpp/client';
2
+ import { Element, parse } from 'ltx';
3
+ import {
4
+ bytesEqual,
5
+ bytesToUtf8,
6
+ fromBase64,
7
+ generateIdentity,
8
+ generateKeyPair,
9
+ randomBytes,
10
+ randomInt,
11
+ sign,
12
+ toBase64,
13
+ toHex,
14
+ utf8ToBytes,
15
+ type IdentityKey,
16
+ type KeyPair,
17
+ } from './crypto';
18
+ import { decodeKeyExchange, encodeKeyExchange } from './protobuf';
19
+ import {
20
+ acceptSession,
21
+ decryptPayload,
22
+ encryptPayload,
23
+ initiateSession,
24
+ ratchetDecrypt,
25
+ ratchetEncrypt,
26
+ type Bundle,
27
+ type Session,
28
+ } from './ratchet';
29
+ import type { OmemoStore } from './store';
30
+ import { NS_OMEMO } from './ns';
31
+
32
+ export { NS_OMEMO };
33
+ const NODE_DEVICES = 'urn:xmpp:omemo:2:devices';
34
+ const NODE_BUNDLES = 'urn:xmpp:omemo:2:bundles';
35
+ const NS_PUBSUB = 'http://jabber.org/protocol/pubsub';
36
+ const NS_PUBSUB_OWNER = 'http://jabber.org/protocol/pubsub#owner';
37
+ const NS_SCE = 'urn:xmpp:sce:1';
38
+ const PREKEY_COUNT = 100;
39
+ const PREKEY_SLICE = 5;
40
+ const DEFAULT_DEVICE_LABEL = 'Mobile';
41
+ const DEVICE_LIST_TTL = 30_000;
42
+
43
+ export const FALLBACK_BODY = 'Encrypted message';
44
+ export type Trust = 'blind' | 'verified' | 'untrusted';
45
+
46
+ interface OwnKeys {
47
+ deviceId: number;
48
+ identity: IdentityKey;
49
+ spk: { id: number; pair: KeyPair; signature: Uint8Array };
50
+ prekeys: Record<number, KeyPair>;
51
+ nextPrekeyId: number;
52
+ }
53
+
54
+ interface DeviceRecord {
55
+ ik: Uint8Array;
56
+ trust: Trust;
57
+ }
58
+
59
+ export interface Decrypted {
60
+ content?: string;
61
+ trust: Trust | 'own';
62
+ error?: string;
63
+ }
64
+
65
+ export interface DeviceInfo {
66
+ id: number;
67
+ fingerprint: string;
68
+ trust: Trust | 'own';
69
+ label?: string;
70
+ }
71
+
72
+ const bare = (jid: string) => jid.split('/')[0]!.toLowerCase();
73
+ const sessionKey = (jid: string, id: number) => `session:${jid}:${id}`;
74
+ const trustKey = (jid: string) => `trust:${jid}`;
75
+ const messageKey = (roomJid: string, id: string) =>
76
+ `message:${bare(roomJid)}:${id}`;
77
+
78
+ export function formatFingerprint(ik: Uint8Array): string {
79
+ return toHex(ik).match(/.{8}/g)!.join(' ');
80
+ }
81
+
82
+ const yieldToUi = () => new Promise<void>((resolve) => setTimeout(resolve, 0));
83
+
84
+ async function generateOwnKeys(): Promise<OwnKeys> {
85
+ const identity = generateIdentity();
86
+ const spkPair = generateKeyPair();
87
+ const prekeys: Record<number, KeyPair> = {};
88
+ for (let id = 1; id <= PREKEY_COUNT; id++) {
89
+ prekeys[id] = generateKeyPair();
90
+ if (id % PREKEY_SLICE === 0) {await yieldToUi();}
91
+ }
92
+ return {
93
+ deviceId: randomInt(1, 0x7fffffff),
94
+ identity,
95
+ spk: { id: 1, pair: spkPair, signature: sign(identity, spkPair.pub) },
96
+ prekeys,
97
+ nextPrekeyId: PREKEY_COUNT + 1,
98
+ };
99
+ }
100
+
101
+ function formField(name: string, value: string, type?: string): Element {
102
+ return xml('field', { var: name, type }, xml('value', {}, value));
103
+ }
104
+
105
+ function isItemNotFound(err: unknown): boolean {
106
+ return (err as { condition?: string })?.condition === 'item-not-found';
107
+ }
108
+
109
+ function isPreconditionNotMet(err: unknown): boolean {
110
+ const condition = (err as { condition?: string })?.condition;
111
+ return condition === 'precondition-not-met' || condition === 'conflict';
112
+ }
113
+
114
+ export class Omemo {
115
+ readonly jid: string;
116
+ private xmpp: Client;
117
+ private readonly store: OmemoStore;
118
+ private readonly label: string;
119
+ private keys: OwnKeys;
120
+ private queue: Promise<unknown> = Promise.resolve();
121
+ private deviceLists = new Map<string, { ids: number[]; at: number }>();
122
+ private publishing?: Promise<boolean>;
123
+
124
+ private constructor(
125
+ xmpp: Client,
126
+ jid: string,
127
+ store: OmemoStore,
128
+ keys: OwnKeys,
129
+ label: string
130
+ ) {
131
+ this.xmpp = xmpp;
132
+ this.jid = jid;
133
+ this.store = store;
134
+ this.keys = keys;
135
+ this.label = label;
136
+ }
137
+
138
+ static async create(
139
+ xmpp: Client,
140
+ jid: string,
141
+ store: OmemoStore,
142
+ options: { label?: string } = {}
143
+ ): Promise<Omemo> {
144
+ let keys = await store.get<OwnKeys>('keys');
145
+ if (!keys) {
146
+ keys = await generateOwnKeys();
147
+ await store.set('keys', keys);
148
+ }
149
+ const omemo = new Omemo(
150
+ xmpp,
151
+ bare(jid),
152
+ store,
153
+ keys,
154
+ options.label || DEFAULT_DEVICE_LABEL
155
+ );
156
+ await omemo.publishBundle();
157
+ await omemo.announceDevice();
158
+ omemo.publishing = Promise.resolve(true);
159
+ return omemo;
160
+ }
161
+
162
+ static async open(
163
+ xmpp: Client,
164
+ jid: string,
165
+ store: OmemoStore,
166
+ options: { label?: string } = {}
167
+ ): Promise<Omemo> {
168
+ let keys = await store.get<OwnKeys>('keys');
169
+ if (!keys) {
170
+ keys = await generateOwnKeys();
171
+ await store.set('keys', keys);
172
+ }
173
+ const omemo = new Omemo(
174
+ xmpp,
175
+ bare(jid),
176
+ store,
177
+ keys,
178
+ options.label || DEFAULT_DEVICE_LABEL
179
+ );
180
+ void omemo.ensurePublished();
181
+ return omemo;
182
+ }
183
+
184
+ published(): Promise<boolean> {
185
+ return this.publishing ?? Promise.resolve(false);
186
+ }
187
+
188
+ ensurePublished(): Promise<boolean> {
189
+ if (this.publishing) {return this.publishing;}
190
+ const attempt = this.publishOnce();
191
+ this.publishing = attempt;
192
+ void attempt.then((ok) => {
193
+ if (!ok && this.publishing === attempt) {this.publishing = undefined;}
194
+ });
195
+ return attempt;
196
+ }
197
+
198
+ private async publishOnce(): Promise<boolean> {
199
+ try {
200
+ await this.publishBundle();
201
+ await this.announceDevice();
202
+ return true;
203
+ } catch (err) {
204
+ console.warn('OMEMO: could not publish this device:', err);
205
+ return false;
206
+ }
207
+ }
208
+
209
+ useClient(xmpp: Client): void {
210
+ this.xmpp = xmpp;
211
+ }
212
+
213
+ get deviceId(): number {
214
+ return this.keys.deviceId;
215
+ }
216
+
217
+ get fingerprint(): string {
218
+ return formatFingerprint(this.keys.identity.pub);
219
+ }
220
+
221
+ private serial<T>(task: () => Promise<T>): Promise<T> {
222
+ const run = this.queue.then(task, task);
223
+ this.queue = run.catch(() => {});
224
+ return run;
225
+ }
226
+
227
+ private async publishWithRecovery(
228
+ node: string,
229
+ itemId: string,
230
+ payload: Element,
231
+ maxItems?: string
232
+ ) {
233
+ try {
234
+ await this.publish(node, itemId, payload, maxItems);
235
+ return;
236
+ } catch (err) {
237
+ if (!isPreconditionNotMet(err)) {throw err;}
238
+ console.warn(`OMEMO: ${node} rejected our publish-options, repairing`);
239
+ }
240
+
241
+ try {
242
+ await this.configureNode(node, maxItems);
243
+ await this.publish(node, itemId, payload, maxItems);
244
+ return;
245
+ } catch (err) {
246
+ if (!isPreconditionNotMet(err)) {throw err;}
247
+ console.warn(`OMEMO: reconfiguring ${node} did not help, recreating it`);
248
+ }
249
+
250
+ await this.deleteNode(node);
251
+ await this.publish(node, itemId, payload, maxItems);
252
+ }
253
+
254
+ private async configureNode(node: string, maxItems?: string) {
255
+ const fields: Element[] = [
256
+ formField(
257
+ 'FORM_TYPE',
258
+ 'http://jabber.org/protocol/pubsub#node_config',
259
+ 'hidden'
260
+ ),
261
+ formField('pubsub#access_model', 'open'),
262
+ formField('pubsub#persist_items', 'true'),
263
+ ];
264
+ if (maxItems) {fields.push(formField('pubsub#max_items', maxItems));}
265
+ await this.xmpp.iqCaller.request(
266
+ xml(
267
+ 'iq',
268
+ { type: 'set' },
269
+ xml(
270
+ 'pubsub',
271
+ { xmlns: NS_PUBSUB_OWNER },
272
+ xml(
273
+ 'configure',
274
+ { node },
275
+ xml('x', { xmlns: 'jabber:x:data', type: 'submit' }, ...fields)
276
+ )
277
+ )
278
+ )
279
+ );
280
+ }
281
+
282
+ private async deleteNode(node: string) {
283
+ await this.xmpp.iqCaller.request(
284
+ xml(
285
+ 'iq',
286
+ { type: 'set' },
287
+ xml('pubsub', { xmlns: NS_PUBSUB_OWNER }, xml('delete', { node }))
288
+ )
289
+ );
290
+ }
291
+
292
+ private async publish(
293
+ node: string,
294
+ itemId: string,
295
+ payload: Element,
296
+ maxItems?: string
297
+ ) {
298
+ const fields: Element[] = [
299
+ formField(
300
+ 'FORM_TYPE',
301
+ 'http://jabber.org/protocol/pubsub#publish-options',
302
+ 'hidden'
303
+ ),
304
+ formField('pubsub#access_model', 'open'),
305
+ ];
306
+ if (maxItems) {fields.push(formField('pubsub#max_items', maxItems));}
307
+ const options = xml(
308
+ 'publish-options',
309
+ {},
310
+ xml('x', { xmlns: 'jabber:x:data', type: 'submit' }, ...fields)
311
+ );
312
+ await this.xmpp.iqCaller.request(
313
+ xml(
314
+ 'iq',
315
+ { type: 'set' },
316
+ xml(
317
+ 'pubsub',
318
+ { xmlns: NS_PUBSUB },
319
+ xml('publish', { node }, xml('item', { id: itemId }, payload)),
320
+ options
321
+ )
322
+ )
323
+ );
324
+ }
325
+
326
+ private async fetchItems(
327
+ jid: string,
328
+ node: string,
329
+ itemId?: string
330
+ ): Promise<Element[]> {
331
+ try {
332
+ const res = await this.xmpp.iqCaller.request(
333
+ xml(
334
+ 'iq',
335
+ { type: 'get', to: jid },
336
+ xml(
337
+ 'pubsub',
338
+ { xmlns: NS_PUBSUB },
339
+ xml(
340
+ 'items',
341
+ { node },
342
+ ...(itemId ? [xml('item', { id: itemId })] : [])
343
+ )
344
+ )
345
+ )
346
+ );
347
+ return (
348
+ res
349
+ .getChild('pubsub', NS_PUBSUB)
350
+ ?.getChild('items')
351
+ ?.getChildren('item') ?? []
352
+ );
353
+ } catch (err) {
354
+ if (isItemNotFound(err)) {return [];}
355
+ throw err;
356
+ }
357
+ }
358
+
359
+ private async fetchDeviceList(jid: string): Promise<Element[]> {
360
+ const items = await this.fetchItems(jid, NODE_DEVICES);
361
+ const list = items.find((i) => i.attrs.id === 'current') ?? items[0];
362
+ return (
363
+ list?.getChild('devices', NS_OMEMO)?.getChildren('device') ?? []
364
+ ).filter((d) => {
365
+ const id = Number(d.attrs.id);
366
+ return Number.isInteger(id) && id > 0 && id <= 0xffffffff;
367
+ });
368
+ }
369
+
370
+ private async deviceIds(jid: string, fresh = false): Promise<number[]> {
371
+ const cached = this.deviceLists.get(jid);
372
+ if (cached && !fresh && Date.now() - cached.at < DEVICE_LIST_TTL) {
373
+ return cached.ids;
374
+ }
375
+ const ids = (await this.fetchDeviceList(jid)).map((d) =>
376
+ Number(d.attrs.id)
377
+ );
378
+ this.deviceLists.set(jid, { ids, at: Date.now() });
379
+ return ids;
380
+ }
381
+
382
+ private async announceDevice() {
383
+ const devices = await this.fetchDeviceList(this.jid);
384
+ if (devices.some((d) => Number(d.attrs.id) === this.deviceId)) {return;}
385
+ const list = xml(
386
+ 'devices',
387
+ { xmlns: NS_OMEMO },
388
+ ...devices.map((d) =>
389
+ xml('device', { id: d.attrs.id, label: d.attrs.label })
390
+ ),
391
+ xml('device', { id: String(this.deviceId), label: this.label })
392
+ );
393
+ await this.publishWithRecovery(NODE_DEVICES, 'current', list);
394
+ this.deviceLists.delete(this.jid);
395
+ }
396
+
397
+ private async publishBundle() {
398
+ const { identity, spk, prekeys } = this.keys;
399
+ const bundle = xml(
400
+ 'bundle',
401
+ { xmlns: NS_OMEMO },
402
+ xml('spk', { id: String(spk.id) }, toBase64(spk.pair.pub)),
403
+ xml('spks', {}, toBase64(spk.signature)),
404
+ xml('ik', {}, toBase64(identity.pub)),
405
+ xml(
406
+ 'prekeys',
407
+ {},
408
+ ...Object.entries(prekeys).map(([id, pk]) =>
409
+ xml('pk', { id }, toBase64(pk.pub))
410
+ )
411
+ )
412
+ );
413
+ await this.publishWithRecovery(
414
+ NODE_BUNDLES,
415
+ String(this.deviceId),
416
+ bundle,
417
+ 'max'
418
+ );
419
+ }
420
+
421
+ private async fetchBundle(jid: string, id: number): Promise<Bundle> {
422
+ const items = await this.fetchItems(jid, NODE_BUNDLES, String(id));
423
+ const el = items
424
+ .find((i) => i.attrs.id === String(id))
425
+ ?.getChild('bundle', NS_OMEMO);
426
+ const spk = el?.getChild('spk');
427
+ const prekeys = el?.getChild('prekeys')?.getChildren('pk') ?? [];
428
+ if (!el || !spk || prekeys.length === 0) {
429
+ throw new Error(`No usable bundle for ${jid}/${id}`);
430
+ }
431
+ const pk = prekeys[randomInt(0, prekeys.length - 1)]!;
432
+ return {
433
+ ik: fromBase64(el.getChildText('ik') ?? ''),
434
+ spkId: Number(spk.attrs.id),
435
+ spk: fromBase64(spk.text()),
436
+ spkSignature: fromBase64(el.getChildText('spks') ?? ''),
437
+ pkId: Number(pk.attrs.id),
438
+ pk: fromBase64(pk.text()),
439
+ };
440
+ }
441
+
442
+
443
+ private async trustOf(
444
+ jid: string,
445
+ id: number,
446
+ ik: Uint8Array
447
+ ): Promise<Trust> {
448
+ const records =
449
+ (await this.store.get<Record<number, DeviceRecord>>(trustKey(jid))) ?? {};
450
+ const known = records[id];
451
+ if (known && bytesEqual(known.ik, ik)) {return known.trust;}
452
+
453
+ const hasVerified = Object.values(records).some(
454
+ (r) => r.trust === 'verified'
455
+ );
456
+ const trust: Trust = known || hasVerified ? 'untrusted' : 'blind';
457
+ records[id] = { ik, trust };
458
+ await this.store.set(trustKey(jid), records);
459
+ return trust;
460
+ }
461
+
462
+ devices(jid: string): Promise<DeviceInfo[]> {
463
+ const target = bare(jid);
464
+ return this.serial(async () => {
465
+ const result: DeviceInfo[] = [];
466
+ const listed = await this.fetchDeviceList(target);
467
+ this.deviceLists.set(target, {
468
+ ids: listed.map((d) => Number(d.attrs.id)),
469
+ at: Date.now(),
470
+ });
471
+ for (const device of listed) {
472
+ const id = Number(device.attrs.id);
473
+ const label = device.attrs.label ? String(device.attrs.label) : undefined;
474
+ if (target === this.jid && id === this.deviceId) {
475
+ result.push({ id, fingerprint: this.fingerprint, trust: 'own', label });
476
+ continue;
477
+ }
478
+ const records =
479
+ (await this.store.get<Record<number, DeviceRecord>>(
480
+ trustKey(target)
481
+ )) ?? {};
482
+ try {
483
+ const ik = records[id]?.ik ?? (await this.fetchBundle(target, id)).ik;
484
+ const trust = await this.trustOf(target, id, ik);
485
+ result.push({ id, fingerprint: formatFingerprint(ik), trust, label });
486
+ } catch (err) {
487
+ console.warn(`OMEMO: no bundle for ${target}/${id}:`, err);
488
+ }
489
+ }
490
+ return result;
491
+ });
492
+ }
493
+
494
+ setTrust(jid: string, id: number, trust: Trust): Promise<void> {
495
+ const target = bare(jid);
496
+ return this.serial(async () => {
497
+ const records =
498
+ (await this.store.get<Record<number, DeviceRecord>>(
499
+ trustKey(target)
500
+ )) ?? {};
501
+ const record = records[id];
502
+ if (!record) {throw new Error('Unknown device');}
503
+ record.trust = trust;
504
+ await this.store.set(trustKey(target), records);
505
+ });
506
+ }
507
+
508
+ private async encryptKey(
509
+ jid: string,
510
+ keyMaterial: Uint8Array
511
+ ): Promise<Element[]> {
512
+ const keys: Element[] = [];
513
+ for (const id of await this.deviceIds(jid)) {
514
+ if (jid === this.jid && id === this.deviceId) {continue;}
515
+ try {
516
+ let session = await this.store.get<Session>(sessionKey(jid, id));
517
+ if (!session) {
518
+ const bundle = await this.fetchBundle(jid, id);
519
+ if ((await this.trustOf(jid, id, bundle.ik)) === 'untrusted') {
520
+ continue;
521
+ }
522
+ session = initiateSession(this.keys.identity, bundle);
523
+ await yieldToUi();
524
+ } else if (
525
+ (await this.trustOf(jid, id, session.peerIk)) === 'untrusted'
526
+ ) {
527
+ continue;
528
+ }
529
+
530
+ const [next, data] = ratchetEncrypt(session, keyMaterial);
531
+ await this.store.set(sessionKey(jid, id), next);
532
+ const kex = next.pendingKex;
533
+ const encoded = kex
534
+ ? encodeKeyExchange({
535
+ ...kex,
536
+ ik: this.keys.identity.pub,
537
+ message: data,
538
+ })
539
+ : data;
540
+ keys.push(
541
+ xml(
542
+ 'key',
543
+ { rid: String(id), kex: kex ? 'true' : undefined },
544
+ toBase64(encoded)
545
+ )
546
+ );
547
+ } catch (err) {
548
+ console.warn(`OMEMO: skipping device ${jid}/${id}:`, err);
549
+ }
550
+ }
551
+ return keys;
552
+ }
553
+
554
+ encryptGroupMessage(
555
+ roomJid: string,
556
+ members: string[],
557
+ content: Element[],
558
+ id: string,
559
+ cleartext: Element[] = []
560
+ ): Promise<Element> {
561
+ return this.serial(async () => {
562
+ const room = bare(roomJid);
563
+ const recipients = Array.from(
564
+ new Set([...members.map(bare), this.jid].filter(Boolean))
565
+ );
566
+
567
+ const envelope = xml(
568
+ 'envelope',
569
+ { xmlns: NS_SCE },
570
+ xml('content', {}, ...content),
571
+ xml('rpad', {}, toBase64(randomBytes(randomInt(0, 48)))),
572
+ xml('time', { stamp: new Date().toISOString() }),
573
+ xml('to', { jid: room }),
574
+ xml('from', { jid: this.jid })
575
+ );
576
+ const { keyMaterial, payload } = encryptPayload(
577
+ utf8ToBytes(envelope.toString())
578
+ );
579
+
580
+ const blocks: Element[] = [];
581
+ const unreachable: string[] = [];
582
+ for (const jid of recipients) {
583
+ let keys = await this.encryptKey(jid, keyMaterial);
584
+ if (keys.length === 0 && jid !== this.jid) {
585
+ this.deviceLists.delete(jid);
586
+ keys = await this.encryptKey(jid, keyMaterial);
587
+ }
588
+ if (keys.length > 0) {
589
+ blocks.push(xml('keys', { jid }, ...keys));
590
+ } else if (jid !== this.jid) {
591
+ unreachable.push(jid);
592
+ }
593
+ }
594
+ if (blocks.length === 0) {
595
+ throw new Error(
596
+ `omemo_no_recipients: no published OMEMO device for ${
597
+ unreachable.join(', ') || 'any member'
598
+ } - they have to open the chat with encryption enabled at least once`
599
+ );
600
+ }
601
+
602
+ const own: Decrypted = {
603
+ content: content.map((c) => c.toString()).join(''),
604
+ trust: 'own',
605
+ };
606
+ await this.store.set(messageKey(room, id), own);
607
+
608
+ return xml(
609
+ 'message',
610
+ { type: 'groupchat', to: room, id },
611
+ xml(
612
+ 'encrypted',
613
+ { xmlns: NS_OMEMO },
614
+ xml('header', { sid: String(this.deviceId) }, ...blocks),
615
+ xml('payload', {}, toBase64(payload))
616
+ ),
617
+ xml('encryption', {
618
+ xmlns: 'urn:xmpp:eme:0',
619
+ namespace: NS_OMEMO,
620
+ name: 'OMEMO',
621
+ }),
622
+ xml('store', { xmlns: 'urn:xmpp:hints' }),
623
+ xml('origin-id', { xmlns: 'urn:xmpp:sid:0', id }),
624
+ xml('body', {}, FALLBACK_BODY),
625
+ ...cleartext
626
+ );
627
+ });
628
+ }
629
+
630
+ private async sendEmpty(jid: string, id: number) {
631
+ const session = await this.store.get<Session>(sessionKey(jid, id));
632
+ if (!session) {return;}
633
+ const [next, data] = ratchetEncrypt(session, new Uint8Array(32));
634
+ await this.store.set(sessionKey(jid, id), next);
635
+ await this.xmpp.send(
636
+ xml(
637
+ 'message',
638
+ { type: 'chat', to: jid, id: `omemo-empty-${Date.now().toString(36)}` },
639
+ xml(
640
+ 'encrypted',
641
+ { xmlns: NS_OMEMO },
642
+ xml(
643
+ 'header',
644
+ { sid: String(this.deviceId) },
645
+ xml(
646
+ 'keys',
647
+ { jid },
648
+ xml('key', { rid: String(id) }, toBase64(data))
649
+ )
650
+ )
651
+ ),
652
+ xml('no-permanent-store', { xmlns: 'urn:xmpp:hints' }),
653
+ xml('no-copy', { xmlns: 'urn:xmpp:hints' }),
654
+ xml('private', { xmlns: 'urn:xmpp:carbons:2' })
655
+ )
656
+ );
657
+ }
658
+
659
+ decrypt(
660
+ el: Element,
661
+ sender: string,
662
+ roomJid: string
663
+ ): Promise<Decrypted> | undefined {
664
+ const encrypted = el.getChild('encrypted', NS_OMEMO);
665
+ if (!encrypted) {return undefined;}
666
+ const from = bare(sender);
667
+ const room = bare(roomJid);
668
+
669
+ return this.serial(async () => {
670
+ const cacheId = el.attrs.id && messageKey(room, el.attrs.id);
671
+ if (cacheId) {
672
+ const cached = await this.store.get<Decrypted>(cacheId);
673
+ if (cached) {return cached;}
674
+ }
675
+ const empty = !encrypted.getChild('payload');
676
+ try {
677
+ const result = await this.decryptUncached(encrypted, from, room);
678
+ if (empty) {return { trust: result.trust };}
679
+ if (cacheId && !result.error) {await this.store.set(cacheId, result);}
680
+ return result;
681
+ } catch (err) {
682
+ if (empty) {return { trust: 'untrusted' };}
683
+ console.warn('OMEMO: decryption failed:', err);
684
+ return { trust: 'untrusted', error: 'omemo_decrypt_failed' };
685
+ }
686
+ });
687
+ }
688
+
689
+ private async decryptUncached(
690
+ encrypted: Element,
691
+ sender: string,
692
+ roomJid: string
693
+ ): Promise<Decrypted> {
694
+ const header = encrypted.getChild('header');
695
+ const sid = Number(header?.attrs.sid);
696
+ if (!header || !Number.isInteger(sid)) {
697
+ throw new Error('Invalid OMEMO header');
698
+ }
699
+ if (sender === this.jid && sid === this.deviceId) {
700
+ return { trust: 'own', error: 'omemo_own_message_not_cached' };
701
+ }
702
+
703
+ const keyEl = header
704
+ .getChildren('keys')
705
+ .filter((k) => bare(k.attrs.jid ?? '') === this.jid)
706
+ .flatMap((k) => k.getChildren('key'))
707
+ .find((k) => Number(k.attrs.rid) === this.deviceId);
708
+ if (!keyEl) {
709
+ return {
710
+ trust: 'untrusted',
711
+ error: 'omemo_not_encrypted_for_this_device',
712
+ };
713
+ }
714
+
715
+ const data = fromBase64(keyEl.text());
716
+ const existing = await this.store.get<Session>(sessionKey(sender, sid));
717
+ let session: Session;
718
+ let keyMaterial: Uint8Array;
719
+ let usedPrekey: number | undefined;
720
+
721
+ if (keyEl.attrs.kex === 'true' || keyEl.attrs.kex === '1') {
722
+ const kex = decodeKeyExchange(data);
723
+ if (
724
+ existing?.kexEk &&
725
+ bytesEqual(existing.kexEk, kex.ek) &&
726
+ bytesEqual(existing.peerIk, kex.ik)
727
+ ) {
728
+ [session, keyMaterial] = ratchetDecrypt(existing, kex.message);
729
+ } else {
730
+ const pk = this.keys.prekeys[kex.pkId];
731
+ if (kex.spkId !== this.keys.spk.id || !pk) {
732
+ throw new Error('Unknown prekey');
733
+ }
734
+ const accepted = acceptSession(
735
+ this.keys.identity,
736
+ this.keys.spk.pair,
737
+ pk,
738
+ kex.ik,
739
+ kex.ek
740
+ );
741
+ [session, keyMaterial] = ratchetDecrypt(accepted, kex.message);
742
+ usedPrekey = kex.pkId;
743
+ }
744
+ } else {
745
+ if (!existing) {throw new Error('No session with the sender device');}
746
+ [session, keyMaterial] = ratchetDecrypt(existing, data);
747
+ }
748
+ session.pendingKex = undefined;
749
+
750
+ const payload = encrypted.getChildText('payload');
751
+ const content = payload
752
+ ? this.openEnvelope(
753
+ decryptPayload(keyMaterial, fromBase64(payload)),
754
+ sender,
755
+ roomJid
756
+ )
757
+ : undefined;
758
+ const trust = await this.trustOf(sender, sid, session.peerIk);
759
+ await this.store.set(sessionKey(sender, sid), session);
760
+
761
+ if (usedPrekey !== undefined) {
762
+ await this.replacePrekey(usedPrekey);
763
+ void this.serial(async () => {
764
+ await this.publishBundle();
765
+ await this.sendEmpty(sender, sid);
766
+ }).catch((err) =>
767
+ console.warn('OMEMO: failed to complete key exchange:', err)
768
+ );
769
+ }
770
+ return { content, trust };
771
+ }
772
+
773
+ private openEnvelope(
774
+ bytes: Uint8Array,
775
+ sender: string,
776
+ roomJid: string
777
+ ): string {
778
+ const envelope = parse(bytesToUtf8(bytes));
779
+ if (!envelope.is('envelope', NS_SCE)) {
780
+ throw new Error('Not an SCE envelope');
781
+ }
782
+ const from = envelope.getChild('from')?.attrs.jid;
783
+ const to = envelope.getChild('to')?.attrs.jid;
784
+ if (!from || bare(from) !== sender) {
785
+ throw new Error('Envelope sender mismatch');
786
+ }
787
+ if (!to || bare(to) !== roomJid) {
788
+ throw new Error('Envelope recipient mismatch');
789
+ }
790
+ return (envelope.getChild('content')?.children ?? [])
791
+ .map((c) => (typeof c === 'string' ? c : (c as Element).toString()))
792
+ .join('');
793
+ }
794
+
795
+ private async replacePrekey(id: number) {
796
+ const keys = { ...this.keys, prekeys: { ...this.keys.prekeys } };
797
+ delete keys.prekeys[id];
798
+ keys.prekeys[keys.nextPrekeyId++] = generateKeyPair();
799
+ await this.store.set('keys', keys);
800
+ this.keys = keys;
801
+ }
802
+ }