@ethora/chat-component-rn 26.6.12 → 26.7.1

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 (281) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +2 -2
  3. package/lib/commonjs/components/AuthForms/Login.js +4 -4
  4. package/lib/commonjs/components/AuthForms/Login.js.map +1 -1
  5. package/lib/commonjs/components/MainComponents/ChatHeader.js +4 -1
  6. package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
  7. package/lib/commonjs/components/MainComponents/ChatWrapper.js +14 -13
  8. package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
  9. package/lib/commonjs/components/MainComponents/LoginWrapper.js +2 -1
  10. package/lib/commonjs/components/MainComponents/LoginWrapper.js.map +1 -1
  11. package/lib/commonjs/components/MainComponents/MediaMessage.js +17 -6
  12. package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
  13. package/lib/commonjs/components/MainComponents/ProfileImagePlaceholder.js +9 -8
  14. package/lib/commonjs/components/MainComponents/ProfileImagePlaceholder.js.map +1 -1
  15. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +4 -1
  16. package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  17. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +8 -7
  18. package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  19. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js +2 -1
  20. package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  21. package/lib/commonjs/components/Modals/NewChatModal/ModalContent.js +2 -1
  22. package/lib/commonjs/components/Modals/NewChatModal/ModalContent.js.map +1 -1
  23. package/lib/commonjs/components/Modals/NewChatModal/UpdNewChatModal.js +8 -0
  24. package/lib/commonjs/components/Modals/NewChatModal/UpdNewChatModal.js.map +1 -1
  25. package/lib/commonjs/components/Modals/UserProfileModal/EditUserModal.js +7 -21
  26. package/lib/commonjs/components/Modals/UserProfileModal/EditUserModal.js.map +1 -1
  27. package/lib/commonjs/components/RoomComponents/ChatRoomItem.js +5 -1
  28. package/lib/commonjs/components/RoomComponents/ChatRoomItem.js.map +1 -1
  29. package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js +10 -2
  30. package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  31. package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js +4 -1
  32. package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  33. package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js +4 -1
  34. package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  35. package/lib/commonjs/components/styled/AudioMessage.js +2 -1
  36. package/lib/commonjs/components/styled/AudioMessage.js.map +1 -1
  37. package/lib/commonjs/components/styled/MessageImage.js +34 -8
  38. package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
  39. package/lib/commonjs/components/styled/VideoMessage.js +7 -1
  40. package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
  41. package/lib/commonjs/context/xmppProvider.js +46 -21
  42. package/lib/commonjs/context/xmppProvider.js.map +1 -1
  43. package/lib/commonjs/helpers/ensureScopedChatCache.js +2 -2
  44. package/lib/commonjs/helpers/ensureScopedChatCache.js.map +1 -1
  45. package/lib/commonjs/helpers/initXmppRooms.js +5 -1
  46. package/lib/commonjs/helpers/initXmppRooms.js.map +1 -1
  47. package/lib/commonjs/helpers/persistCrypto.js +122 -0
  48. package/lib/commonjs/helpers/persistCrypto.js.map +1 -0
  49. package/lib/commonjs/helpers/pickImageAsset.js +68 -0
  50. package/lib/commonjs/helpers/pickImageAsset.js.map +1 -0
  51. package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js +130 -17
  52. package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js.map +1 -1
  53. package/lib/commonjs/helpers/secureFileUrl.js +101 -0
  54. package/lib/commonjs/helpers/secureFileUrl.js.map +1 -0
  55. package/lib/commonjs/helpers/secureKeyValue.js +83 -0
  56. package/lib/commonjs/helpers/secureKeyValue.js.map +1 -0
  57. package/lib/commonjs/helpers/secureStoreRuntime.js +59 -0
  58. package/lib/commonjs/helpers/secureStoreRuntime.js.map +1 -0
  59. package/lib/commonjs/helpers/secureUserStorage.js +75 -0
  60. package/lib/commonjs/helpers/secureUserStorage.js.map +1 -0
  61. package/lib/commonjs/hooks/useChatWrapperInit.js +3 -3
  62. package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
  63. package/lib/commonjs/hooks/useFileToken.js +10 -0
  64. package/lib/commonjs/hooks/useFileToken.js.map +1 -0
  65. package/lib/commonjs/hooks/useLogout.js +15 -7
  66. package/lib/commonjs/hooks/useLogout.js.map +1 -1
  67. package/lib/commonjs/hooks/useSendMessage.js +23 -13
  68. package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
  69. package/lib/commonjs/main.js +25 -0
  70. package/lib/commonjs/main.js.map +1 -1
  71. package/lib/commonjs/networking/api-requests/auth.api.js +99 -54
  72. package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
  73. package/lib/commonjs/networking/api-requests/roomMembers.api.js +1 -1
  74. package/lib/commonjs/networking/api-requests/roomMembers.api.js.map +1 -1
  75. package/lib/commonjs/networking/api-requests/rooms.api.js +9 -9
  76. package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
  77. package/lib/commonjs/networking/api-requests/user.api.js +9 -9
  78. package/lib/commonjs/networking/api-requests/user.api.js.map +1 -1
  79. package/lib/commonjs/networking/apiClient.js +62 -112
  80. package/lib/commonjs/networking/apiClient.js.map +1 -1
  81. package/lib/commonjs/networking/authRefresh.js +322 -0
  82. package/lib/commonjs/networking/authRefresh.js.map +1 -0
  83. package/lib/commonjs/networking/xmppClient.js +33 -5
  84. package/lib/commonjs/networking/xmppClient.js.map +1 -1
  85. package/lib/commonjs/roomStore/chatSettingsSlice.js +23 -6
  86. package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
  87. package/lib/commonjs/roomStore/persistence.js +29 -15
  88. package/lib/commonjs/roomStore/persistence.js.map +1 -1
  89. package/lib/commonjs/roomStore/roomsSlice.js +2 -0
  90. package/lib/commonjs/roomStore/roomsSlice.js.map +1 -1
  91. package/lib/module/components/AuthForms/Login.js +4 -4
  92. package/lib/module/components/AuthForms/Login.js.map +1 -1
  93. package/lib/module/components/MainComponents/ChatHeader.js +4 -1
  94. package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
  95. package/lib/module/components/MainComponents/ChatWrapper.js +14 -13
  96. package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
  97. package/lib/module/components/MainComponents/LoginWrapper.js +2 -1
  98. package/lib/module/components/MainComponents/LoginWrapper.js.map +1 -1
  99. package/lib/module/components/MainComponents/MediaMessage.js +17 -6
  100. package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
  101. package/lib/module/components/MainComponents/ProfileImagePlaceholder.js +9 -8
  102. package/lib/module/components/MainComponents/ProfileImagePlaceholder.js.map +1 -1
  103. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +4 -1
  104. package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
  105. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +8 -7
  106. package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
  107. package/lib/module/components/Modals/FilePreviewModal/PdfView.js +2 -1
  108. package/lib/module/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
  109. package/lib/module/components/Modals/NewChatModal/ModalContent.js +2 -1
  110. package/lib/module/components/Modals/NewChatModal/ModalContent.js.map +1 -1
  111. package/lib/module/components/Modals/NewChatModal/UpdNewChatModal.js +8 -0
  112. package/lib/module/components/Modals/NewChatModal/UpdNewChatModal.js.map +1 -1
  113. package/lib/module/components/Modals/UserProfileModal/EditUserModal.js +9 -23
  114. package/lib/module/components/Modals/UserProfileModal/EditUserModal.js.map +1 -1
  115. package/lib/module/components/RoomComponents/ChatRoomItem.js +5 -1
  116. package/lib/module/components/RoomComponents/ChatRoomItem.js.map +1 -1
  117. package/lib/module/components/RoomComponents/styled/LastMessageFile.js +10 -2
  118. package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
  119. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +4 -1
  120. package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
  121. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +4 -1
  122. package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
  123. package/lib/module/components/styled/AudioMessage.js +2 -1
  124. package/lib/module/components/styled/AudioMessage.js.map +1 -1
  125. package/lib/module/components/styled/MessageImage.js +34 -8
  126. package/lib/module/components/styled/MessageImage.js.map +1 -1
  127. package/lib/module/components/styled/VideoMessage.js +7 -1
  128. package/lib/module/components/styled/VideoMessage.js.map +1 -1
  129. package/lib/module/context/xmppProvider.js +45 -21
  130. package/lib/module/context/xmppProvider.js.map +1 -1
  131. package/lib/module/helpers/ensureScopedChatCache.js +2 -2
  132. package/lib/module/helpers/ensureScopedChatCache.js.map +1 -1
  133. package/lib/module/helpers/initXmppRooms.js +5 -1
  134. package/lib/module/helpers/initXmppRooms.js.map +1 -1
  135. package/lib/module/helpers/persistCrypto.js +115 -0
  136. package/lib/module/helpers/persistCrypto.js.map +1 -0
  137. package/lib/module/helpers/pickImageAsset.js +63 -0
  138. package/lib/module/helpers/pickImageAsset.js.map +1 -0
  139. package/lib/module/helpers/resolveInitBeforeLoadUser.js +130 -16
  140. package/lib/module/helpers/resolveInitBeforeLoadUser.js.map +1 -1
  141. package/lib/module/helpers/secureFileUrl.js +91 -0
  142. package/lib/module/helpers/secureFileUrl.js.map +1 -0
  143. package/lib/module/helpers/secureKeyValue.js +75 -0
  144. package/lib/module/helpers/secureKeyValue.js.map +1 -0
  145. package/lib/module/helpers/secureStoreRuntime.js +52 -0
  146. package/lib/module/helpers/secureStoreRuntime.js.map +1 -0
  147. package/lib/module/helpers/secureUserStorage.js +72 -0
  148. package/lib/module/helpers/secureUserStorage.js.map +1 -0
  149. package/lib/module/hooks/useChatWrapperInit.js +3 -3
  150. package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
  151. package/lib/module/hooks/useFileToken.js +5 -0
  152. package/lib/module/hooks/useFileToken.js.map +1 -0
  153. package/lib/module/hooks/useLogout.js +15 -7
  154. package/lib/module/hooks/useLogout.js.map +1 -1
  155. package/lib/module/hooks/useSendMessage.js +24 -14
  156. package/lib/module/hooks/useSendMessage.js.map +1 -1
  157. package/lib/module/main.js +12 -1
  158. package/lib/module/main.js.map +1 -1
  159. package/lib/module/networking/api-requests/auth.api.js +96 -53
  160. package/lib/module/networking/api-requests/auth.api.js.map +1 -1
  161. package/lib/module/networking/api-requests/roomMembers.api.js +1 -1
  162. package/lib/module/networking/api-requests/roomMembers.api.js.map +1 -1
  163. package/lib/module/networking/api-requests/rooms.api.js +9 -9
  164. package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
  165. package/lib/module/networking/api-requests/user.api.js +8 -7
  166. package/lib/module/networking/api-requests/user.api.js.map +1 -1
  167. package/lib/module/networking/apiClient.js +61 -113
  168. package/lib/module/networking/apiClient.js.map +1 -1
  169. package/lib/module/networking/authRefresh.js +311 -0
  170. package/lib/module/networking/authRefresh.js.map +1 -0
  171. package/lib/module/networking/xmppClient.js +33 -5
  172. package/lib/module/networking/xmppClient.js.map +1 -1
  173. package/lib/module/roomStore/chatSettingsSlice.js +23 -6
  174. package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
  175. package/lib/module/roomStore/persistence.js +30 -15
  176. package/lib/module/roomStore/persistence.js.map +1 -1
  177. package/lib/module/roomStore/roomsSlice.js +2 -0
  178. package/lib/module/roomStore/roomsSlice.js.map +1 -1
  179. package/lib/typescript/components/AuthForms/Login.d.ts.map +1 -1
  180. package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
  181. package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
  182. package/lib/typescript/components/MainComponents/LoginWrapper.d.ts.map +1 -1
  183. package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
  184. package/lib/typescript/components/MainComponents/ProfileImagePlaceholder.d.ts.map +1 -1
  185. package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
  186. package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
  187. package/lib/typescript/components/Modals/FilePreviewModal/PdfView.d.ts.map +1 -1
  188. package/lib/typescript/components/Modals/NewChatModal/ModalContent.d.ts +4 -2
  189. package/lib/typescript/components/Modals/NewChatModal/ModalContent.d.ts.map +1 -1
  190. package/lib/typescript/components/Modals/NewChatModal/UpdNewChatModal.d.ts.map +1 -1
  191. package/lib/typescript/components/Modals/UserProfileModal/EditUserModal.d.ts.map +1 -1
  192. package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts.map +1 -1
  193. package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
  194. package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
  195. package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
  196. package/lib/typescript/components/styled/AudioMessage.d.ts.map +1 -1
  197. package/lib/typescript/components/styled/MessageImage.d.ts +8 -0
  198. package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
  199. package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
  200. package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
  201. package/lib/typescript/helpers/initXmppRooms.d.ts.map +1 -1
  202. package/lib/typescript/helpers/persistCrypto.d.ts +13 -0
  203. package/lib/typescript/helpers/persistCrypto.d.ts.map +1 -0
  204. package/lib/typescript/helpers/pickImageAsset.d.ts +27 -0
  205. package/lib/typescript/helpers/pickImageAsset.d.ts.map +1 -0
  206. package/lib/typescript/helpers/resolveInitBeforeLoadUser.d.ts.map +1 -1
  207. package/lib/typescript/helpers/secureFileUrl.d.ts +7 -0
  208. package/lib/typescript/helpers/secureFileUrl.d.ts.map +1 -0
  209. package/lib/typescript/helpers/secureKeyValue.d.ts +13 -0
  210. package/lib/typescript/helpers/secureKeyValue.d.ts.map +1 -0
  211. package/lib/typescript/helpers/secureStoreRuntime.d.ts +29 -0
  212. package/lib/typescript/helpers/secureStoreRuntime.d.ts.map +1 -0
  213. package/lib/typescript/helpers/secureUserStorage.d.ts +23 -0
  214. package/lib/typescript/helpers/secureUserStorage.d.ts.map +1 -0
  215. package/lib/typescript/hooks/useFileToken.d.ts +2 -0
  216. package/lib/typescript/hooks/useFileToken.d.ts.map +1 -0
  217. package/lib/typescript/hooks/useLogout.d.ts.map +1 -1
  218. package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
  219. package/lib/typescript/main.d.ts +6 -1
  220. package/lib/typescript/main.d.ts.map +1 -1
  221. package/lib/typescript/networking/api-requests/auth.api.d.ts +3 -27
  222. package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
  223. package/lib/typescript/networking/api-requests/user.api.d.ts.map +1 -1
  224. package/lib/typescript/networking/apiClient.d.ts +12 -6
  225. package/lib/typescript/networking/apiClient.d.ts.map +1 -1
  226. package/lib/typescript/networking/authRefresh.d.ts +92 -0
  227. package/lib/typescript/networking/authRefresh.d.ts.map +1 -0
  228. package/lib/typescript/networking/xmppClient.d.ts +6 -0
  229. package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
  230. package/lib/typescript/roomStore/chatSettingsSlice.d.ts +16 -0
  231. package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
  232. package/lib/typescript/roomStore/persistence.d.ts.map +1 -1
  233. package/lib/typescript/roomStore/roomsSlice.d.ts.map +1 -1
  234. package/lib/typescript/types/types.d.ts +17 -0
  235. package/lib/typescript/types/types.d.ts.map +1 -1
  236. package/package.json +8 -1
  237. package/src/components/AuthForms/Login.tsx +3 -3
  238. package/src/components/MainComponents/ChatHeader.tsx +4 -1
  239. package/src/components/MainComponents/ChatWrapper.tsx +14 -19
  240. package/src/components/MainComponents/LoginWrapper.tsx +1 -0
  241. package/src/components/MainComponents/MediaMessage.tsx +14 -4
  242. package/src/components/MainComponents/ProfileImagePlaceholder.tsx +16 -9
  243. package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +4 -1
  244. package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +8 -7
  245. package/src/components/Modals/FilePreviewModal/PdfView.tsx +2 -1
  246. package/src/components/Modals/NewChatModal/ModalContent.tsx +6 -3
  247. package/src/components/Modals/NewChatModal/UpdNewChatModal.tsx +12 -1
  248. package/src/components/Modals/UserProfileModal/EditUserModal.tsx +9 -25
  249. package/src/components/RoomComponents/ChatRoomItem.tsx +9 -1
  250. package/src/components/RoomComponents/styled/LastMessageFile.tsx +14 -2
  251. package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +4 -1
  252. package/src/components/RoomComponents/styled/LastMessageVideo.tsx +4 -1
  253. package/src/components/styled/AudioMessage.tsx +2 -1
  254. package/src/components/styled/MessageImage.tsx +45 -6
  255. package/src/components/styled/VideoMessage.tsx +10 -1
  256. package/src/context/xmppProvider.tsx +47 -23
  257. package/src/helpers/ensureScopedChatCache.ts +2 -2
  258. package/src/helpers/initXmppRooms.tsx +5 -1
  259. package/src/helpers/persistCrypto.ts +127 -0
  260. package/src/helpers/pickImageAsset.ts +68 -0
  261. package/src/helpers/resolveInitBeforeLoadUser.ts +125 -18
  262. package/src/helpers/secureFileUrl.ts +103 -0
  263. package/src/helpers/secureKeyValue.ts +80 -0
  264. package/src/helpers/secureStoreRuntime.ts +62 -0
  265. package/src/helpers/secureUserStorage.ts +80 -0
  266. package/src/hooks/useChatWrapperInit.ts +3 -3
  267. package/src/hooks/useFileToken.ts +7 -0
  268. package/src/hooks/useLogout.tsx +15 -9
  269. package/src/hooks/useSendMessage.tsx +28 -13
  270. package/src/main.ts +22 -1
  271. package/src/networking/api-requests/auth.api.ts +125 -52
  272. package/src/networking/api-requests/roomMembers.api.ts +1 -1
  273. package/src/networking/api-requests/rooms.api.ts +9 -9
  274. package/src/networking/api-requests/user.api.ts +8 -7
  275. package/src/networking/apiClient.ts +68 -123
  276. package/src/networking/authRefresh.ts +430 -0
  277. package/src/networking/xmppClient.ts +37 -5
  278. package/src/roomStore/chatSettingsSlice.ts +31 -9
  279. package/src/roomStore/persistence.ts +39 -19
  280. package/src/roomStore/roomsSlice.ts +2 -0
  281. package/src/types/types.ts +17 -0
@@ -94,6 +94,13 @@ interface MamInFlightEntry {
94
94
  startedAt: number;
95
95
  }
96
96
 
97
+ /**
98
+ * Floor between two credential rotations driven by reconnects. Well under
99
+ * the XMPP password's one-hour life, but high enough that a link flapping
100
+ * every few seconds rotates once, not thirty times.
101
+ */
102
+ const CREDENTIALS_REFRESH_MIN_INTERVAL_MS = 60_000;
103
+
97
104
  export type XmppCredentialsProvider = () => Promise<{
98
105
  username: string;
99
106
  password: string;
@@ -137,6 +144,12 @@ export class XmppClient {
137
144
  // derived XMPP password after idle). Triggers the credentialsProvider
138
145
  // refresh path in reconnect().
139
146
  lastAuthError: 'not-authorized' | null = null;
147
+ /**
148
+ * When the credentials currently held were last (re)minted. Drives the
149
+ * throttle in `reconnect()` so a flapping connection can't rotate the
150
+ * session once per flap.
151
+ */
152
+ private credentialsRefreshedAt = 0;
140
153
  private credentialsProvider: XmppCredentialsProvider | null = null;
141
154
  private credentialsRefreshInFlight: Promise<void> | null = null;
142
155
  // Fired by the provider on every 'online' so it can re-join MUC rooms
@@ -700,14 +713,32 @@ export class XmppClient {
700
713
  console.log('Attempting to reconnect xmpp client...');
701
714
 
702
715
  try {
703
- // If the last failure was a SASL `not-authorized` (typically a stale
704
- // JWT-derived XMPP password after idle), try to fetch fresh creds
705
- // before retrying. Without this, reconnect retries with the same
706
- // stale password forever and the user has to kill the app.
707
- if (this.lastAuthError === 'not-authorized' && this.credentialsProvider) {
716
+ // Re-mint credentials BEFORE reconnecting.
717
+ //
718
+ // This deliberately no longer waits for a SASL `not-authorized`.
719
+ // The XMPP password expires after an hour, and an expiry does NOT
720
+ // drop an established connection — authentication happens once, at
721
+ // bind. So by the time anything disconnects us (a network blip, a
722
+ // sleeping device, a server restart) the password we still hold may
723
+ // well be dead, and we have no way to know. Reconnecting with it
724
+ // burns a guaranteed-failed bind plus a backoff delay before the
725
+ // old `lastAuthError` path could even start recovering.
726
+ //
727
+ // Throttled, because reconnects are not rare: a flapping link would
728
+ // otherwise rotate the whole session (access + refresh + file token
729
+ // + XMPP password) once per flap. A SASL failure bypasses the
730
+ // throttle — there the credentials are known bad.
731
+ const authFailed = this.lastAuthError === 'not-authorized';
732
+ const staleCreds =
733
+ Date.now() - this.credentialsRefreshedAt >=
734
+ CREDENTIALS_REFRESH_MIN_INTERVAL_MS;
735
+
736
+ if (this.credentialsProvider && (authFailed || staleCreds)) {
708
737
  try {
709
738
  await this.refreshCredentialsOnce();
710
739
  } catch (err) {
740
+ // Offline is the common case here — reconnect with what we
741
+ // have and let the next attempt try again.
711
742
  console.warn(
712
743
  'XMPP credential refresh failed; reconnecting with cached creds',
713
744
  err
@@ -763,6 +794,7 @@ export class XmppClient {
763
794
  // creds really are stale we simply get not-authorized again and
764
795
  // retry, which is the confirmed-good 26.5.7 behaviour.
765
796
  this.updateCredentials(fresh.username, fresh.password);
797
+ this.credentialsRefreshedAt = Date.now();
766
798
  this.lastAuthError = null;
767
799
  }
768
800
  } finally {
@@ -11,8 +11,7 @@ import {
11
11
  } from '../types/types';
12
12
  import { Iso639_1Codes } from '../types/models/language.model';
13
13
  import type { TranslateMode } from '../utils/translateModePolicy';
14
- import { localStorageConstants } from '../helpers/constants/LOCAL_STORAGE';
15
- import { asyncLocalStorage } from '../hooks/useLocalStorage';
14
+ import { secureUserStorage } from '../helpers/secureUserStorage';
16
15
 
17
16
  export interface ChatState {
18
17
  user: User;
@@ -40,6 +39,7 @@ export const unpackAndTransform = (input?: User): User => {
40
39
  _id: input?._id || '',
41
40
  walletAddress: input?.defaultWallet?.walletAddress || '',
42
41
  xmppPassword: input?.xmppPassword || '',
42
+ fileToken: input?.fileToken || '',
43
43
  refreshToken: input?.refreshToken || '',
44
44
  firstName: input?.firstName || '',
45
45
  lastName: input?.lastName || '',
@@ -73,6 +73,7 @@ const initialState: ChatState = {
73
73
  _id: '',
74
74
  walletAddress: '',
75
75
  xmppPassword: '',
76
+ fileToken: '',
76
77
  refreshToken: '',
77
78
  firstName: '',
78
79
  lastName: '',
@@ -111,9 +112,7 @@ const initialState: ChatState = {
111
112
  const reducers = {
112
113
  setUser: (state: WritableDraft<ChatState>, action: PayloadAction<User>) => {
113
114
  state.user = unpackAndTransform(action.payload);
114
- asyncLocalStorage(localStorageConstants.ETHORA_USER).set(
115
- unpackAndTransform(action.payload)
116
- );
115
+ secureUserStorage().set(unpackAndTransform(action.payload));
117
116
  },
118
117
  updateUser(state: WritableDraft<ChatState>, action: PayloadAction<{ updates: Partial<User> }>) {
119
118
  const { updates } = action.payload;
@@ -146,14 +145,37 @@ const reducers = {
146
145
  setSelectedUser: (state: WritableDraft<ChatState>, action: PayloadAction<IUser | undefined>) => {
147
146
  state.selectedUser = action.payload;
148
147
  },
148
+ /**
149
+ * Token rotation. Dispatched from ONE place only — `authRefresh` —
150
+ * which awaits the AsyncStorage write immediately afterwards, before
151
+ * resolving its promise.
152
+ *
153
+ * The reducer used to fire its own best-effort write here. That was
154
+ * both redundant and racy (two writers, no ordering), and a
155
+ * fire-and-forget write can be lost if the app dies right after a
156
+ * rotation — which on a backend with reuse detection means the next
157
+ * launch presents a burned token. Persistence now belongs to the
158
+ * caller, which can actually await it.
159
+ */
149
160
  refreshTokens: (
150
161
  state: WritableDraft<ChatState>,
151
- action: PayloadAction<{ token: string; refreshToken: string }>
162
+ action: PayloadAction<{
163
+ token: string;
164
+ refreshToken: string;
165
+ xmppPassword?: string;
166
+ fileToken?: string;
167
+ }>
152
168
  ) => {
153
169
  state.user.refreshToken = action.payload.refreshToken;
154
170
  state.user.token = action.payload.token;
155
- // AsyncStorage is async; fire-and-forget so the reducer stays sync.
156
- asyncLocalStorage(localStorageConstants.ETHORA_USER).set(state.user);
171
+
172
+ if (action.payload.xmppPassword) {
173
+ state.user.xmppPassword = action.payload.xmppPassword;
174
+ }
175
+
176
+ if (action.payload.fileToken) {
177
+ state.user.fileToken = action.payload.fileToken;
178
+ }
157
179
  },
158
180
  logout: (state: WritableDraft<ChatState>) => {
159
181
  state.user = unpackAndTransform();
@@ -161,7 +183,7 @@ const reducers = {
161
183
  state.client = undefined;
162
184
  state.langSource = undefined;
163
185
  state.translateMode = undefined;
164
- asyncLocalStorage(localStorageConstants.ETHORA_USER).remove();
186
+ secureUserStorage().remove();
165
187
  },
166
188
  setLangSource: (
167
189
  state: WritableDraft<ChatState>,
@@ -1,6 +1,7 @@
1
1
  import AsyncStorage from '@react-native-async-storage/async-storage';
2
2
  import { Middleware } from '@reduxjs/toolkit';
3
3
  import { IMessage, IRoom, User } from '../types/types';
4
+ import { encryptForPersist, decryptFromPersist } from '../helpers/persistCrypto';
4
5
 
5
6
  // -------------------------------------------------------------------
6
7
  // Lightweight RN persistence layer. Mirrors what redux-persist gives
@@ -11,7 +12,11 @@ import { IMessage, IRoom, User } from '../types/types';
11
12
  // - cap each room's messages to the most recent MESSAGE_LIMIT (100)
12
13
  // - debounced writes (200ms) on relevant action types
13
14
  //
14
- // Not encrypted. We rely on AsyncStorage's per-app sandbox.
15
+ // AES-256-CBC at rest (see `helpers/persistCrypto.ts`): the JSON blob
16
+ // under each key below is encrypted before it reaches AsyncStorage, with
17
+ // the symmetric key held in the platform Keychain/Keystore. `user` here
18
+ // is already secret-free (see `sanitizeUser`) — the encryption is for
19
+ // the message bodies in `rooms`, which aren't.
15
20
  // -------------------------------------------------------------------
16
21
 
17
22
  const KEY_CHAT = '@ethora/persist:chatSettingStore';
@@ -199,21 +204,27 @@ export const persistenceMiddleware: Middleware = (storeAPI) => (next) => (
199
204
 
200
205
  if (writeTimer) {clearTimeout(writeTimer);}
201
206
  writeTimer = setTimeout(() => {
202
- try {
203
- const state = storeAPI.getState();
204
- const chatPayload: PersistedChatState = {
205
- user: sanitizeUser(state.chatSettingStore?.user) as User,
206
- };
207
- const roomsPayload: PersistedRoomsState = {
208
- rooms: sanitizeRooms(state.rooms?.rooms || {}),
209
- };
210
- AsyncStorage.multiSet([
211
- [KEY_CHAT, JSON.stringify(chatPayload)],
212
- [KEY_ROOMS, JSON.stringify(roomsPayload)],
213
- ]).catch((e) => console.warn('persist write failed', e));
214
- } catch (e) {
215
- console.warn('persist serialize failed', e);
216
- }
207
+ (async () => {
208
+ try {
209
+ const state = storeAPI.getState();
210
+ const chatPayload: PersistedChatState = {
211
+ user: sanitizeUser(state.chatSettingStore?.user) as User,
212
+ };
213
+ const roomsPayload: PersistedRoomsState = {
214
+ rooms: sanitizeRooms(state.rooms?.rooms || {}),
215
+ };
216
+ const [chatCipher, roomsCipher] = await Promise.all([
217
+ encryptForPersist(JSON.stringify(chatPayload)),
218
+ encryptForPersist(JSON.stringify(roomsPayload)),
219
+ ]);
220
+ await AsyncStorage.multiSet([
221
+ [KEY_CHAT, chatCipher],
222
+ [KEY_ROOMS, roomsCipher],
223
+ ]);
224
+ } catch (e) {
225
+ console.warn('persist write failed', e);
226
+ }
227
+ })();
217
228
  }, 200);
218
229
 
219
230
  return result;
@@ -233,9 +244,18 @@ export async function readPersistedState(): Promise<{
233
244
  KEY_CHAT,
234
245
  KEY_ROOMS,
235
246
  ]);
236
- const chat = chatRaw[1] ? (JSON.parse(chatRaw[1]) as PersistedChatState) : null;
237
- const rooms = roomsRaw[1]
238
- ? (JSON.parse(roomsRaw[1]) as PersistedRoomsState)
247
+ const [chatPlain, roomsPlain] = await Promise.all([
248
+ chatRaw[1] ? decryptFromPersist(chatRaw[1]) : Promise.resolve(null),
249
+ roomsRaw[1] ? decryptFromPersist(roomsRaw[1]) : Promise.resolve(null),
250
+ ]);
251
+ // `decryptFromPersist` returns null both for "nothing stored" and
252
+ // for "stored value isn't a valid envelope for the current key" —
253
+ // notably a plaintext blob left over from a pre-encryption install.
254
+ // Either way there is nothing safe to parse, so the caller treats it
255
+ // as a cold start and re-hydrates from the server.
256
+ const chat = chatPlain ? (JSON.parse(chatPlain) as PersistedChatState) : null;
257
+ const rooms = roomsPlain
258
+ ? (JSON.parse(roomsPlain) as PersistedRoomsState)
239
259
  : null;
240
260
  return { chat, rooms };
241
261
  } catch (e) {
@@ -317,6 +317,8 @@ const reducers = {
317
317
  : [];
318
318
  state.rooms[roomData.jid] = {
319
319
  ...roomData,
320
+ icon: roomData.icon !== undefined ? roomData.icon : existing?.icon,
321
+ roomBg: roomData.roomBg !== undefined ? roomData.roomBg : existing?.roomBg,
320
322
  messages:
321
323
  existingMessages.length > 0 ? existingMessages : incomingMessages,
322
324
  lastViewedTimestamp: lastViewed,
@@ -105,6 +105,7 @@ export interface User {
105
105
  ];
106
106
  appId: string;
107
107
  xmppPassword: string;
108
+ fileToken?: string;
108
109
 
109
110
  homeScreen?: string;
110
111
  registrationChannelType?: string;
@@ -455,9 +456,25 @@ export interface IConfig {
455
456
  defaultLogin?: boolean;
456
457
  refreshTokens?: {
457
458
  enabled: boolean;
459
+ /**
460
+ * Host-owned rotation. When set, the SDK calls this INSTEAD of
461
+ * `/users/login/refresh` — always, including during bootstrap — and
462
+ * never rotates the Ethora refresh token itself. Serialised through
463
+ * the same lock as the built-in path.
464
+ *
465
+ * Return `refreshToken` whenever the host's flow actually rotates an
466
+ * Ethora refresh token: the backend now treats a re-presented one as
467
+ * theft, so omitting it leaves a burned token in storage that ends
468
+ * the session the next time it is used. It stays optional because
469
+ * some hosts re-mint an access token from a different authority
470
+ * (SuperTokens, an owner-session endpoint) and hold no Ethora
471
+ * refresh token at all.
472
+ */
458
473
  refreshFunction?: () => Promise<{
459
474
  accessToken: string;
460
475
  refreshToken?: string;
476
+ fileToken?: string;
477
+ xmppPassword?: string;
461
478
  } | null>;
462
479
  };
463
480