@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
@@ -2,8 +2,9 @@
2
2
 
3
3
  import axios from 'axios';
4
4
  import { store } from '../roomStore';
5
- import { logout, refreshTokens } from '../roomStore/chatSettingsSlice';
5
+ import { logout } from '../roomStore/chatSettingsSlice';
6
6
  import { installAxiosCapture } from '../utils/devLogger';
7
+ import { refreshAuthTokens, isRefreshFatalError } from './authRefresh';
7
8
 
8
9
  // Per product-code-policy: no compiled-in Ethora endpoints or tokens.
9
10
  // Consumers must call `setBaseURL(baseUrl, appToken)` (or pass
@@ -19,10 +20,24 @@ const http = axios.create({
19
20
  // Dev-logger hook: capture every request + response on the shared
20
21
  // `http` instance.
21
22
  installAxiosCapture(http);
23
+ const stripApiVersion = baseUrl => baseUrl.replace(/\/+$/, '').replace(/\/v\d+$/, '');
24
+ export const normalizeApiPath = path => {
25
+ if (!path) {
26
+ return path;
27
+ }
28
+ if (/^https?:\/\//i.test(path)) {
29
+ return path;
30
+ }
31
+ if (/^\/v\d+\//.test(path)) {
32
+ return path;
33
+ }
34
+ return `/v1${path.startsWith('/') ? '' : '/'}${path}`;
35
+ };
22
36
  export function setBaseURL(baseUrl, appToken) {
23
- if (baseUrl && baseUrl !== currentBaseURL) {
24
- currentBaseURL = baseUrl;
25
- http.defaults.baseURL = baseUrl;
37
+ const normalized = baseUrl ? stripApiVersion(baseUrl) : baseUrl;
38
+ if (normalized && normalized !== currentBaseURL) {
39
+ currentBaseURL = normalized;
40
+ http.defaults.baseURL = normalized;
26
41
  }
27
42
  if (appToken && appToken !== currentAppToken) {
28
43
  currentAppToken = appToken;
@@ -34,132 +49,65 @@ export function getCurrentAppToken() {
34
49
  export function getCurrentBaseURL() {
35
50
  return currentBaseURL;
36
51
  }
52
+
53
+ /**
54
+ * @deprecated Import `refreshAuthTokens` from `./authRefresh` instead.
55
+ *
56
+ * Kept as a thin forwarder so existing call sites keep working while
57
+ * they migrate. Declared as a function (not `const refresh =
58
+ * refreshAuthTokens`) on purpose: this module and `authRefresh` import
59
+ * each other, and a const would capture the binding before the other
60
+ * module finished evaluating.
61
+ */
37
62
  export function refresh() {
38
- return new Promise((resolve, reject) => {
39
- const user = store.getState().chatSettingStore.user;
40
- try {
41
- http.post('/users/login/refresh', {}, {
42
- headers: {
43
- Authorization: user.refreshToken
44
- }
45
- }).then(response => {
46
- store.dispatch(refreshTokens({
47
- token: response.data.token,
48
- refreshToken: response.data.refreshToken
49
- }));
50
- // BUGFIX: previously the `.then` only dispatched and never
51
- // resolved, so every `await refresh()` callsite hung forever
52
- // on the happy path. Resolve with the axios response shape so
53
- // the interceptor can read `tokens.data.token` to retry.
54
- resolve(response);
55
- }).catch(error => {
56
- reject(error);
57
- });
58
- } catch (error) {
59
- // BUGFIX: previously the outer-try catch dispatched logout but
60
- // never rejected, so a synchronous throw from `http.post` (e.g.
61
- // a bad URL) would hang the promise. Reject explicitly.
62
- console.log('errr');
63
- store.dispatch(logout());
64
- reject(error);
65
- }
66
- });
63
+ return refreshAuthTokens();
67
64
  }
68
- let isRefreshing = false;
69
- let failedQueue = [];
70
- const addRequestToQueue = config => {
71
- return new Promise((resolve, reject) => {
72
- failedQueue.push({
73
- resolve,
74
- reject,
75
- config
76
- });
77
- });
78
- };
79
- const processQueue = newAccessToken => {
80
- for (const request of failedQueue) {
81
- if (newAccessToken) {
82
- request.config.headers.Authorization = newAccessToken;
83
- }
84
- request.resolve(http(request.config));
85
- }
86
- failedQueue = [];
87
- };
88
65
  http.interceptors.response.use(response => response, async error => {
89
- // BUGFIX: the original condition was `if (!enabled)` — inverted, so
90
- // turning `refreshTokens.enabled: true` actually DISABLED the refresh
91
- // path on 401. Use the correct sense.
92
- if (!store.getState().chatSettingStore?.config?.refreshTokens?.enabled) {
93
- // BUGFIX: previously this branch fell off the end of the async
94
- // function and returned `undefined`, which axios treats as a
95
- // resolved-with-undefined response. Callers expecting an error
96
- // got `undefined` instead, silently swallowing every failure
97
- // when the refresh path isn't configured. Re-reject so the
98
- // original error reaches the caller.
99
- return Promise.reject(error);
100
- }
101
66
  const originalRequest = error.config;
102
67
  const status = error.response?.status;
103
68
 
104
69
  // Refresh paths only fire on 401. Anything else passes through.
105
- if (!error.response || status !== 401) {
70
+ if (!error.response || status !== 401 || !originalRequest) {
106
71
  return Promise.reject(error);
107
72
  }
108
- if (store.getState().chatSettingStore?.config?.refreshTokens?.refreshFunction) {
109
- // Consumer-supplied refresh function. Call it, dispatch the new
110
- // tokens, retry the original request.
111
- try {
112
- const fn = store.getState().chatSettingStore?.config?.refreshTokens?.refreshFunction;
113
- if (!fn) return Promise.reject(error);
114
- const result = await fn();
115
- const {
116
- refreshToken: newRefreshToken,
117
- accessToken
118
- } = result || {
119
- refreshToken: '',
120
- accessToken: ''
121
- };
122
- store.dispatch(refreshTokens({
123
- token: accessToken,
124
- refreshToken: newRefreshToken || ''
125
- }));
126
- // BUGFIX: previously this branch returned `undefined` after
127
- // dispatching, so the original request was never retried and
128
- // the caller resolved with undefined. Stamp the new token on
129
- // the original config and replay.
130
- if (originalRequest?.headers) {
131
- originalRequest.headers.Authorization = accessToken;
132
- }
133
- return http(originalRequest);
134
- } catch (refreshErr) {
135
- return Promise.reject(refreshErr);
136
- }
73
+
74
+ // Never retry twice, and never touch the auth endpoints themselves —
75
+ // both would recurse. (`_retry` was set but never checked before,
76
+ // so a request could bounce between 401 and replay indefinitely.)
77
+ if (originalRequest._retry || originalRequest.url === '/v1/users/login/refresh' || originalRequest.url === '/v1/users/login') {
78
+ return Promise.reject(error);
137
79
  }
80
+ const refreshConfig = store.getState().chatSettingStore?.config?.refreshTokens;
138
81
 
139
- // Built-in `/users/login/refresh` path. Skip if the failing request
140
- // IS the refresh / login itself — otherwise we'd loop forever.
141
- if (originalRequest.url === '/users/login/refresh' || originalRequest.url === '/users/login') {
82
+ // BUGFIX (kept): the original condition here was inverted, so
83
+ // `refreshTokens.enabled: true` actually DISABLED the refresh path.
84
+ if (!refreshConfig?.enabled) {
142
85
  return Promise.reject(error);
143
86
  }
144
- originalRequest._retry = true;
145
- if (isRefreshing) {
146
- const retryOriginalRequest = addRequestToQueue(originalRequest);
147
- return retryOriginalRequest;
87
+
88
+ // Nothing to rotate with — reject rather than firing a doomed
89
+ // refresh that would look like a bad-token event to the backend.
90
+ const hasRefreshableSession = Boolean(refreshConfig.refreshFunction || store.getState().chatSettingStore.user?.refreshToken);
91
+ if (!hasRefreshableSession) {
92
+ return Promise.reject(error);
148
93
  }
149
- isRefreshing = true;
94
+ originalRequest._retry = true;
150
95
  try {
151
- const tokens = await refresh();
152
- // (was: console.log('tokens', tokens) — dumped the full response
153
- // incl. access/refresh/ws tokens on every refresh; removed.)
154
- isRefreshing = false;
155
- originalRequest.headers.Authorization = tokens.data.token;
156
- processQueue(tokens.data.token);
96
+ // One shared rotation for every 401 in flight — `refreshAuthTokens`
97
+ // dedupes internally, which replaces the old isRefreshing/
98
+ // failedQueue machinery entirely.
99
+ const tokens = await refreshAuthTokens();
100
+ originalRequest.headers = originalRequest.headers || {};
101
+ originalRequest.headers.Authorization = tokens.token;
157
102
  return http(originalRequest);
158
103
  } catch (refreshErr) {
159
- isRefreshing = false;
160
- // BUGFIX: previously this returned the error (resolving the
161
- // promise with the error as value) instead of rejecting, so
162
- // axios callers got the error as a "successful" response body.
104
+ // Only a genuinely dead session logs the user out. A network
105
+ // blip, a 5xx, or a REFRESH_IN_PROGRESS race must leave the
106
+ // session alone — logging out on those is exactly the mass-logout
107
+ // failure mode the new backend scheme would otherwise cause.
108
+ if (isRefreshFatalError(refreshErr)) {
109
+ store.dispatch(logout());
110
+ }
163
111
  return Promise.reject(refreshErr);
164
112
  }
165
113
  });
@@ -1 +1 @@
1
- {"version":3,"names":["axios","store","logout","refreshTokens","installAxiosCapture","DEFAULT_BASE_URL","currentBaseURL","currentAppToken","http","create","baseURL","setBaseURL","baseUrl","appToken","defaults","getCurrentAppToken","getCurrentBaseURL","refresh","Promise","resolve","reject","user","getState","chatSettingStore","post","headers","Authorization","refreshToken","then","response","dispatch","token","data","catch","error","console","log","isRefreshing","failedQueue","addRequestToQueue","config","push","processQueue","newAccessToken","request","interceptors","use","enabled","originalRequest","status","refreshFunction","fn","result","newRefreshToken","accessToken","refreshErr","url","_retry","retryOriginalRequest","tokens"],"sourceRoot":"../../../src","sources":["networking/apiClient.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["axios","store","logout","installAxiosCapture","refreshAuthTokens","isRefreshFatalError","DEFAULT_BASE_URL","currentBaseURL","currentAppToken","http","create","baseURL","stripApiVersion","baseUrl","replace","normalizeApiPath","path","test","startsWith","setBaseURL","appToken","normalized","defaults","getCurrentAppToken","getCurrentBaseURL","refresh","interceptors","response","use","error","originalRequest","config","status","Promise","reject","_retry","url","refreshConfig","getState","chatSettingStore","refreshTokens","enabled","hasRefreshableSession","Boolean","refreshFunction","user","refreshToken","tokens","headers","Authorization","token","refreshErr","dispatch"],"sourceRoot":"../../../src","sources":["networking/apiClient.ts"],"mappings":"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","ignoreList":[]}
@@ -0,0 +1,311 @@
1
+ "use strict";
2
+
3
+ import http from './apiClient';
4
+ import { store } from '../roomStore';
5
+ import { refreshTokens } from '../roomStore/chatSettingsSlice';
6
+ import { secureUserStorage } from '../helpers/secureUserStorage';
7
+ import { pushLog } from '../utils/devLogger';
8
+
9
+ /**
10
+ * THE single refresh-token rotation point for the SDK.
11
+ *
12
+ * The backend now runs refresh-token ROTATION with REUSE DETECTION:
13
+ * every successful `/users/login/refresh` burns the presented refresh
14
+ * token and issues a new one. Presenting an already-rotated token is
15
+ * indistinguishable, server-side, from a stolen token — so it is
16
+ * treated as theft and (once the backend leaves monitor mode and
17
+ * enables enforcing mode) kills the whole token family.
18
+ *
19
+ * That puts three hard obligations on the client:
20
+ *
21
+ * 1. Persist the NEW refreshToken from every response, immediately,
22
+ * before any other logic can throw and drop it on the floor.
23
+ * 2. Never rotate concurrently. Two parallel refreshes both start
24
+ * from the same token; the loser presents a burned one and looks
25
+ * like an attacker. React Native is single-process, so a shared
26
+ * in-flight promise is the whole lock — no Web Locks needed (the
27
+ * web build of this component does need them, see its own
28
+ * `authRefresh.ts`).
29
+ * 3. Route EVERY refresh path through here. One "side" refresh
30
+ * elsewhere in the codebase breaks the scheme for everyone.
31
+ *
32
+ * Callers must also stop treating 401 as "log out". Two of the four
33
+ * refresh error codes are race signals, not auth failures — see
34
+ * `RefreshErrorCode` below and `RefreshFatalError`.
35
+ */
36
+
37
+ /**
38
+ * Thrown when the session is genuinely dead and the caller must do a
39
+ * hard logout (clear storage, drop XMPP, send the user to login).
40
+ *
41
+ * Anything else this module rejects with — network errors, 5xx, an
42
+ * unrecognised 401 — is NOT fatal and must NOT trigger a logout.
43
+ */
44
+ export class RefreshFatalError extends Error {
45
+ constructor(code, message) {
46
+ super(message || `Refresh failed: ${code}`);
47
+ this.name = 'RefreshFatalError';
48
+ this.code = code;
49
+ // Required for `instanceof` to survive TS's ES5 class-extends-builtin
50
+ // downlevelling, which RN's babel preset still applies.
51
+ Object.setPrototypeOf(this, RefreshFatalError.prototype);
52
+ }
53
+ }
54
+ export const isRefreshFatalError = error => error instanceof RefreshFatalError || error?.name === 'RefreshFatalError';
55
+ const REFRESH_ENDPOINT = '/v1/users/login/refresh';
56
+
57
+ /** `REFRESH_IN_PROGRESS` retry budget, per backend guidance (~300ms x 2-3). */
58
+ const IN_PROGRESS_MAX_ATTEMPTS = 3;
59
+ const IN_PROGRESS_BASE_DELAY_MS = 300;
60
+ /**
61
+ * Jitter matters: after a network blip every client in the app retries
62
+ * on the same schedule and would hammer the rotation mutex in lockstep.
63
+ */
64
+ const IN_PROGRESS_JITTER_MS = 150;
65
+ const KNOWN_CODES = ['REFRESH_IN_PROGRESS', 'REFRESH_TOKEN_ALREADY_ROTATED', 'REFRESH_TOKEN_REUSE_DETECTED', 'REFRESH_TOKEN_NOT_FOUND'];
66
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
67
+ const inProgressDelay = attempt => IN_PROGRESS_BASE_DELAY_MS * attempt + Math.floor(Math.random() * IN_PROGRESS_JITTER_MS);
68
+
69
+ /**
70
+ * All four codes come back as HTTP 401, so the status alone tells us
71
+ * nothing — the code in the body is the only signal.
72
+ *
73
+ * TODO(backend-confirm): the exact location of `code` in the payload is
74
+ * not documented yet. Until it is, probe every shape the API uses
75
+ * elsewhere and take the first hit. Once confirmed, collapse this to
76
+ * the single real path.
77
+ */
78
+ export const parseRefreshErrorCode = error => {
79
+ const response = error?.response;
80
+ if (!response) {
81
+ return null;
82
+ }
83
+ const data = response.data;
84
+ const candidates = [data?.code, data?.error?.code, data?.errors?.[0]?.code, data?.error, data?.message, response.headers?.['x-error-code']];
85
+ for (const candidate of candidates) {
86
+ if (typeof candidate !== 'string') {
87
+ continue;
88
+ }
89
+ const match = KNOWN_CODES.find(code => candidate.includes(code));
90
+ if (match) {
91
+ return match;
92
+ }
93
+ }
94
+ return null;
95
+ };
96
+ const getStoreUser = () => store.getState().chatSettingStore?.user || {};
97
+
98
+ /**
99
+ * The refresh token as of RIGHT NOW. Read at the moment of use, never
100
+ * captured ahead of time — that is the single-process equivalent of the
101
+ * web build's "re-read inside the lock".
102
+ *
103
+ * Redux is the source of truth in RN (one process, one store);
104
+ * AsyncStorage is only consulted as a cold-start fallback, before
105
+ * `resolveInitBeforeLoadUser` has hydrated the store.
106
+ */
107
+ const readCurrentRefreshToken = async () => {
108
+ const fromStore = getStoreUser().refreshToken;
109
+ if (fromStore) {
110
+ return fromStore;
111
+ }
112
+ const stored = await secureUserStorage().get();
113
+ return stored?.refreshToken || '';
114
+ };
115
+
116
+ /**
117
+ * Persist BEFORE resolving. The dispatch updates redux (and, until
118
+ * phase 6 lands, fires its own best-effort AsyncStorage write); the
119
+ * awaited write below is the one that actually guarantees the rotated
120
+ * token survives the app being killed a moment later. Losing it means
121
+ * the next launch presents a burned token — i.e. looks like theft.
122
+ */
123
+ const persistTokens = async result => {
124
+ store.dispatch(refreshTokens({
125
+ token: result.token,
126
+ refreshToken: result.refreshToken,
127
+ xmppPassword: result.xmppPassword,
128
+ fileToken: result.fileToken
129
+ }));
130
+ try {
131
+ // Write the post-dispatch snapshot rather than a read-modify-write
132
+ // merge, so this can't race the reducer's own write back to a
133
+ // stale copy of the surrounding user fields.
134
+ await secureUserStorage().set(store.getState().chatSettingStore.user);
135
+ } catch (error) {
136
+ pushLog('warn', 'authRefresh: failed to persist rotated tokens', error);
137
+ }
138
+ };
139
+
140
+ /**
141
+ * A rotation that happened somewhere we didn't drive — currently only
142
+ * reachable via the ALREADY_ROTATED recovery path, kept separate so the
143
+ * web build and this one stay structurally identical.
144
+ */
145
+ const adoptStoredTokens = async () => {
146
+ const stored = await secureUserStorage().get();
147
+ if (!stored?.refreshToken || !stored?.token) {
148
+ return null;
149
+ }
150
+ const user = getStoreUser();
151
+ if (user.refreshToken !== stored.refreshToken) {
152
+ store.dispatch(refreshTokens({
153
+ token: stored.token,
154
+ refreshToken: stored.refreshToken
155
+ }));
156
+ }
157
+ return {
158
+ token: stored.token,
159
+ refreshToken: stored.refreshToken,
160
+ xmppPassword: stored.xmppPassword,
161
+ fileToken: stored.fileToken
162
+ };
163
+ };
164
+
165
+ /** Host-supplied rotation, mirrors `IConfig['refreshTokens']['refreshFunction']`. */
166
+
167
+ const runConsumerRefresh = async refreshFunction => {
168
+ const refreshed = await refreshFunction();
169
+ if (!refreshed?.accessToken) {
170
+ throw new Error('Custom refresh function did not return an access token');
171
+ }
172
+ if (!refreshed.refreshToken) {
173
+ // Not fatal — some hosts still run non-rotating backends — but on a
174
+ // rotating one this silently keeps a burned token around, which is
175
+ // exactly the failure the new scheme punishes.
176
+ pushLog('warn', 'authRefresh: custom refreshFunction returned no refreshToken; ' + 'keeping the existing one. On a rotating backend this will be ' + 'seen as token reuse.');
177
+ }
178
+ const result = {
179
+ token: refreshed.accessToken,
180
+ refreshToken: refreshed.refreshToken || (await readCurrentRefreshToken()) || '',
181
+ xmppPassword: refreshed.xmppPassword,
182
+ fileToken: refreshed.fileToken
183
+ };
184
+ await persistTokens(result);
185
+ return result;
186
+ };
187
+ const requestRotation = async refreshToken => {
188
+ const response = await http.post(REFRESH_ENDPOINT, {}, {
189
+ headers: {
190
+ Authorization: refreshToken
191
+ }
192
+ });
193
+ const result = {
194
+ token: response?.data?.token || '',
195
+ refreshToken: response?.data?.refreshToken || '',
196
+ wsToken: response?.data?.wsToken,
197
+ xmppPassword: response?.data?.xmppPassword,
198
+ fileToken: response?.data?.fileToken
199
+ };
200
+ if (!result.token || !result.refreshToken) {
201
+ throw new Error('Refresh response did not contain both tokens');
202
+ }
203
+
204
+ // Persist first, resolve second. Every early return between here and
205
+ // the caller is a chance to lose the rotation.
206
+ await persistTokens(result);
207
+ return result;
208
+ };
209
+ const performRefresh = async overrideToken => {
210
+ const consumerRefresh = store.getState().chatSettingStore?.config?.refreshTokens?.refreshFunction;
211
+ if (consumerRefresh) {
212
+ // A configured host function wins unconditionally — including over
213
+ // an explicit bootstrap token. Hosts that embed this SDK often own
214
+ // the Ethora refresh token themselves (their own storage, their own
215
+ // rotation) and only hand us a copy; rotating it here would burn
216
+ // THEIR token behind their back and leave them holding a dead one.
217
+ //
218
+ // It still has to be serialised — it hits the same backend mutex —
219
+ // hence running it inside the shared in-flight promise.
220
+ return runConsumerRefresh(consumerRefresh);
221
+ }
222
+ for (let attempt = 1; attempt <= IN_PROGRESS_MAX_ATTEMPTS; attempt++) {
223
+ const refreshToken = overrideToken || (await readCurrentRefreshToken());
224
+ if (!refreshToken) {
225
+ // Deliberately NOT fatal: a missing token mid-bootstrap must not
226
+ // nuke a session that is still being hydrated.
227
+ throw new Error('Refresh token is missing');
228
+ }
229
+ try {
230
+ return await requestRotation(refreshToken);
231
+ } catch (error) {
232
+ const code = parseRefreshErrorCode(error);
233
+ if (code === 'REFRESH_IN_PROGRESS') {
234
+ if (attempt < IN_PROGRESS_MAX_ATTEMPTS) {
235
+ await sleep(inProgressDelay(attempt));
236
+ continue;
237
+ }
238
+ // Budget spent. Whoever holds the mutex has very likely
239
+ // finished and written a newer token — take it if so. Skipped
240
+ // for an explicit token: it may belong to a different session
241
+ // than whatever is currently in storage.
242
+ const adopted = overrideToken ? null : await adoptStoredTokens();
243
+ if (adopted && adopted.refreshToken !== refreshToken) {
244
+ return adopted;
245
+ }
246
+ throw error;
247
+ }
248
+ if (code === 'REFRESH_TOKEN_ALREADY_ROTATED') {
249
+ // Our copy is stale. If something already wrote a newer token,
250
+ // this is a benign race — use it. Otherwise the session really
251
+ // is unrecoverable.
252
+ const adopted = overrideToken ? null : await adoptStoredTokens();
253
+ if (adopted && adopted.refreshToken !== refreshToken) {
254
+ return adopted;
255
+ }
256
+ throw new RefreshFatalError(code);
257
+ }
258
+ if (code === 'REFRESH_TOKEN_REUSE_DETECTED' || code === 'REFRESH_TOKEN_NOT_FOUND') {
259
+ throw new RefreshFatalError(code);
260
+ }
261
+
262
+ // Network error, 5xx, plain 401 without a code — pass through
263
+ // untouched. The caller must not log out on these.
264
+ throw error;
265
+ }
266
+ }
267
+
268
+ // Unreachable: the loop either returns or throws.
269
+ throw new Error('Refresh attempts exhausted');
270
+ };
271
+ let inflight = null;
272
+
273
+ /**
274
+ * Rotate the tokens. Concurrent callers share ONE request — this is the
275
+ * lock the new backend scheme requires.
276
+ *
277
+ * Rejects with `RefreshFatalError` when the session is dead (caller
278
+ * should hard-logout) and with a plain error otherwise (caller should
279
+ * surface the failure and leave the session alone).
280
+ */
281
+ export function refreshAuthTokens(options) {
282
+ if (inflight) {
283
+ // Whatever that rotation produces is at least as fresh as anything
284
+ // this caller could have asked for.
285
+ return inflight;
286
+ }
287
+ inflight = performRefresh(options?.refreshToken).finally(() => {
288
+ inflight = null;
289
+ });
290
+ return inflight;
291
+ }
292
+
293
+ /**
294
+ * Fire-and-forget variant for call sites that only want "make sure the
295
+ * tokens are fresh" and have no meaningful error handling — chat
296
+ * bootstrap, XMPP reconnect. Never rejects.
297
+ */
298
+ export async function refreshAuthTokensQuietly() {
299
+ try {
300
+ return await refreshAuthTokens();
301
+ } catch (error) {
302
+ pushLog(isRefreshFatalError(error) ? 'error' : 'warn', 'authRefresh: background refresh failed', error);
303
+ return null;
304
+ }
305
+ }
306
+
307
+ /** Test seam — drops any shared in-flight promise between cases. */
308
+ export function __resetAuthRefreshStateForTests() {
309
+ inflight = null;
310
+ }
311
+ //# sourceMappingURL=authRefresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"names":["http","store","refreshTokens","secureUserStorage","pushLog","RefreshFatalError","Error","constructor","code","message","name","Object","setPrototypeOf","prototype","isRefreshFatalError","error","REFRESH_ENDPOINT","IN_PROGRESS_MAX_ATTEMPTS","IN_PROGRESS_BASE_DELAY_MS","IN_PROGRESS_JITTER_MS","KNOWN_CODES","sleep","ms","Promise","resolve","setTimeout","inProgressDelay","attempt","Math","floor","random","parseRefreshErrorCode","response","data","candidates","errors","headers","candidate","match","find","includes","getStoreUser","getState","chatSettingStore","user","readCurrentRefreshToken","fromStore","refreshToken","stored","get","persistTokens","result","dispatch","token","xmppPassword","fileToken","set","adoptStoredTokens","runConsumerRefresh","refreshFunction","refreshed","accessToken","requestRotation","post","Authorization","wsToken","performRefresh","overrideToken","consumerRefresh","config","adopted","inflight","refreshAuthTokens","options","finally","refreshAuthTokensQuietly","__resetAuthRefreshStateForTests"],"sourceRoot":"../../../src","sources":["networking/authRefresh.ts"],"mappings":"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","ignoreList":[]}
@@ -74,6 +74,12 @@ function isNotAuthorizedError(err) {
74
74
  const message = (err?.message || err?.text || '').toString();
75
75
  return /not-authorized|SASLError/i.test(message);
76
76
  }
77
+ /**
78
+ * Floor between two credential rotations driven by reconnects. Well under
79
+ * the XMPP password's one-hour life, but high enough that a link flapping
80
+ * every few seconds rotates once, not thirty times.
81
+ */
82
+ const CREDENTIALS_REFRESH_MIN_INTERVAL_MS = 60_000;
77
83
  export class XmppClient {
78
84
  host = '';
79
85
  service = '';
@@ -107,6 +113,12 @@ export class XmppClient {
107
113
  // derived XMPP password after idle). Triggers the credentialsProvider
108
114
  // refresh path in reconnect().
109
115
  lastAuthError = null;
116
+ /**
117
+ * When the credentials currently held were last (re)minted. Drives the
118
+ * throttle in `reconnect()` so a flapping connection can't rotate the
119
+ * session once per flap.
120
+ */
121
+ credentialsRefreshedAt = 0;
110
122
  credentialsProvider = null;
111
123
  credentialsRefreshInFlight = null;
112
124
  // Fired by the provider on every 'online' so it can re-join MUC rooms
@@ -654,14 +666,29 @@ export class XmppClient {
654
666
  this.reconnecting = true;
655
667
  console.log('Attempting to reconnect xmpp client...');
656
668
  try {
657
- // If the last failure was a SASL `not-authorized` (typically a stale
658
- // JWT-derived XMPP password after idle), try to fetch fresh creds
659
- // before retrying. Without this, reconnect retries with the same
660
- // stale password forever and the user has to kill the app.
661
- if (this.lastAuthError === 'not-authorized' && this.credentialsProvider) {
669
+ // Re-mint credentials BEFORE reconnecting.
670
+ //
671
+ // This deliberately no longer waits for a SASL `not-authorized`.
672
+ // The XMPP password expires after an hour, and an expiry does NOT
673
+ // drop an established connection — authentication happens once, at
674
+ // bind. So by the time anything disconnects us (a network blip, a
675
+ // sleeping device, a server restart) the password we still hold may
676
+ // well be dead, and we have no way to know. Reconnecting with it
677
+ // burns a guaranteed-failed bind plus a backoff delay before the
678
+ // old `lastAuthError` path could even start recovering.
679
+ //
680
+ // Throttled, because reconnects are not rare: a flapping link would
681
+ // otherwise rotate the whole session (access + refresh + file token
682
+ // + XMPP password) once per flap. A SASL failure bypasses the
683
+ // throttle — there the credentials are known bad.
684
+ const authFailed = this.lastAuthError === 'not-authorized';
685
+ const staleCreds = Date.now() - this.credentialsRefreshedAt >= CREDENTIALS_REFRESH_MIN_INTERVAL_MS;
686
+ if (this.credentialsProvider && (authFailed || staleCreds)) {
662
687
  try {
663
688
  await this.refreshCredentialsOnce();
664
689
  } catch (err) {
690
+ // Offline is the common case here — reconnect with what we
691
+ // have and let the next attempt try again.
665
692
  console.warn('XMPP credential refresh failed; reconnecting with cached creds', err);
666
693
  }
667
694
  }
@@ -718,6 +745,7 @@ export class XmppClient {
718
745
  // creds really are stale we simply get not-authorized again and
719
746
  // retry, which is the confirmed-good 26.5.7 behaviour.
720
747
  this.updateCredentials(fresh.username, fresh.password);
748
+ this.credentialsRefreshedAt = Date.now();
721
749
  this.lastAuthError = null;
722
750
  }
723
751
  } finally {