@ethora/chat-component-rn 26.6.12 → 26.6.13
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.
- package/CHANGELOG.md +5 -0
- package/README.md +2 -2
- package/lib/commonjs/components/AuthForms/Login.js +2 -1
- package/lib/commonjs/components/AuthForms/Login.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatHeader.js +4 -1
- package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatWrapper.js +14 -13
- package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/commonjs/components/MainComponents/LoginWrapper.js +2 -1
- package/lib/commonjs/components/MainComponents/LoginWrapper.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MediaMessage.js +17 -6
- package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js +4 -1
- package/lib/commonjs/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +8 -7
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js +2 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/ChatRoomItem.js +5 -1
- package/lib/commonjs/components/RoomComponents/ChatRoomItem.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js +10 -2
- package/lib/commonjs/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js +4 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js +4 -1
- package/lib/commonjs/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
- package/lib/commonjs/components/styled/AudioMessage.js +2 -1
- package/lib/commonjs/components/styled/AudioMessage.js.map +1 -1
- package/lib/commonjs/components/styled/MessageImage.js +34 -8
- package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
- package/lib/commonjs/components/styled/VideoMessage.js +7 -1
- package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +44 -18
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/commonjs/helpers/initXmppRooms.js +5 -1
- package/lib/commonjs/helpers/initXmppRooms.js.map +1 -1
- package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js +126 -12
- package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js.map +1 -1
- package/lib/commonjs/helpers/secureFileUrl.js +101 -0
- package/lib/commonjs/helpers/secureFileUrl.js.map +1 -0
- package/lib/commonjs/hooks/useChatWrapperInit.js +3 -3
- package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
- package/lib/commonjs/hooks/useFileToken.js +10 -0
- package/lib/commonjs/hooks/useFileToken.js.map +1 -0
- package/lib/commonjs/hooks/useSendMessage.js +23 -13
- package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
- package/lib/commonjs/main.js +25 -0
- package/lib/commonjs/main.js.map +1 -1
- package/lib/commonjs/networking/api-requests/auth.api.js +99 -54
- package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
- package/lib/commonjs/networking/api-requests/roomMembers.api.js +1 -1
- package/lib/commonjs/networking/api-requests/roomMembers.api.js.map +1 -1
- package/lib/commonjs/networking/api-requests/rooms.api.js +9 -9
- package/lib/commonjs/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/commonjs/networking/api-requests/user.api.js +9 -9
- package/lib/commonjs/networking/api-requests/user.api.js.map +1 -1
- package/lib/commonjs/networking/apiClient.js +62 -112
- package/lib/commonjs/networking/apiClient.js.map +1 -1
- package/lib/commonjs/networking/authRefresh.js +323 -0
- package/lib/commonjs/networking/authRefresh.js.map +1 -0
- package/lib/commonjs/networking/xmppClient.js +33 -5
- package/lib/commonjs/networking/xmppClient.js.map +1 -1
- package/lib/commonjs/roomStore/chatSettingsSlice.js +20 -2
- package/lib/commonjs/roomStore/chatSettingsSlice.js.map +1 -1
- package/lib/module/components/AuthForms/Login.js +2 -1
- package/lib/module/components/AuthForms/Login.js.map +1 -1
- package/lib/module/components/MainComponents/ChatHeader.js +4 -1
- package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/module/components/MainComponents/ChatWrapper.js +14 -13
- package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/module/components/MainComponents/LoginWrapper.js +2 -1
- package/lib/module/components/MainComponents/LoginWrapper.js.map +1 -1
- package/lib/module/components/MainComponents/MediaMessage.js +17 -6
- package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js +4 -1
- package/lib/module/components/Modals/ChatProfileModal/ChatProfileModal.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +8 -7
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/PdfView.js +2 -1
- package/lib/module/components/Modals/FilePreviewModal/PdfView.js.map +1 -1
- package/lib/module/components/RoomComponents/ChatRoomItem.js +5 -1
- package/lib/module/components/RoomComponents/ChatRoomItem.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessageFile.js +10 -2
- package/lib/module/components/RoomComponents/styled/LastMessageFile.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js +4 -1
- package/lib/module/components/RoomComponents/styled/LastMessagePhoto.js.map +1 -1
- package/lib/module/components/RoomComponents/styled/LastMessageVideo.js +4 -1
- package/lib/module/components/RoomComponents/styled/LastMessageVideo.js.map +1 -1
- package/lib/module/components/styled/AudioMessage.js +2 -1
- package/lib/module/components/styled/AudioMessage.js.map +1 -1
- package/lib/module/components/styled/MessageImage.js +34 -8
- package/lib/module/components/styled/MessageImage.js.map +1 -1
- package/lib/module/components/styled/VideoMessage.js +7 -1
- package/lib/module/components/styled/VideoMessage.js.map +1 -1
- package/lib/module/context/xmppProvider.js +43 -18
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/module/helpers/initXmppRooms.js +5 -1
- package/lib/module/helpers/initXmppRooms.js.map +1 -1
- package/lib/module/helpers/resolveInitBeforeLoadUser.js +126 -11
- package/lib/module/helpers/resolveInitBeforeLoadUser.js.map +1 -1
- package/lib/module/helpers/secureFileUrl.js +91 -0
- package/lib/module/helpers/secureFileUrl.js.map +1 -0
- package/lib/module/hooks/useChatWrapperInit.js +3 -3
- package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
- package/lib/module/hooks/useFileToken.js +5 -0
- package/lib/module/hooks/useFileToken.js.map +1 -0
- package/lib/module/hooks/useSendMessage.js +24 -14
- package/lib/module/hooks/useSendMessage.js.map +1 -1
- package/lib/module/main.js +12 -1
- package/lib/module/main.js.map +1 -1
- package/lib/module/networking/api-requests/auth.api.js +96 -53
- package/lib/module/networking/api-requests/auth.api.js.map +1 -1
- package/lib/module/networking/api-requests/roomMembers.api.js +1 -1
- package/lib/module/networking/api-requests/roomMembers.api.js.map +1 -1
- package/lib/module/networking/api-requests/rooms.api.js +9 -9
- package/lib/module/networking/api-requests/rooms.api.js.map +1 -1
- package/lib/module/networking/api-requests/user.api.js +8 -7
- package/lib/module/networking/api-requests/user.api.js.map +1 -1
- package/lib/module/networking/apiClient.js +61 -113
- package/lib/module/networking/apiClient.js.map +1 -1
- package/lib/module/networking/authRefresh.js +312 -0
- package/lib/module/networking/authRefresh.js.map +1 -0
- package/lib/module/networking/xmppClient.js +33 -5
- package/lib/module/networking/xmppClient.js.map +1 -1
- package/lib/module/roomStore/chatSettingsSlice.js +20 -2
- package/lib/module/roomStore/chatSettingsSlice.js.map +1 -1
- package/lib/typescript/components/AuthForms/Login.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/LoginWrapper.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ChatProfileModal/ChatProfileModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/PdfView.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/ChatRoomItem.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageFile.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessagePhoto.d.ts.map +1 -1
- package/lib/typescript/components/RoomComponents/styled/LastMessageVideo.d.ts.map +1 -1
- package/lib/typescript/components/styled/AudioMessage.d.ts.map +1 -1
- package/lib/typescript/components/styled/MessageImage.d.ts +8 -0
- package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
- package/lib/typescript/components/styled/VideoMessage.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/lib/typescript/helpers/initXmppRooms.d.ts.map +1 -1
- package/lib/typescript/helpers/resolveInitBeforeLoadUser.d.ts.map +1 -1
- package/lib/typescript/helpers/secureFileUrl.d.ts +7 -0
- package/lib/typescript/helpers/secureFileUrl.d.ts.map +1 -0
- package/lib/typescript/hooks/useFileToken.d.ts +2 -0
- package/lib/typescript/hooks/useFileToken.d.ts.map +1 -0
- package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/main.d.ts +6 -1
- package/lib/typescript/main.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/auth.api.d.ts +3 -27
- package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/user.api.d.ts.map +1 -1
- package/lib/typescript/networking/apiClient.d.ts +12 -6
- package/lib/typescript/networking/apiClient.d.ts.map +1 -1
- package/lib/typescript/networking/authRefresh.d.ts +92 -0
- package/lib/typescript/networking/authRefresh.d.ts.map +1 -0
- package/lib/typescript/networking/xmppClient.d.ts +6 -0
- package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
- package/lib/typescript/roomStore/chatSettingsSlice.d.ts +16 -0
- package/lib/typescript/roomStore/chatSettingsSlice.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +17 -0
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/AuthForms/Login.tsx +1 -0
- package/src/components/MainComponents/ChatHeader.tsx +4 -1
- package/src/components/MainComponents/ChatWrapper.tsx +14 -19
- package/src/components/MainComponents/LoginWrapper.tsx +1 -0
- package/src/components/MainComponents/MediaMessage.tsx +14 -4
- package/src/components/Modals/ChatProfileModal/ChatProfileModal.tsx +4 -1
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +8 -7
- package/src/components/Modals/FilePreviewModal/PdfView.tsx +2 -1
- package/src/components/RoomComponents/ChatRoomItem.tsx +9 -1
- package/src/components/RoomComponents/styled/LastMessageFile.tsx +14 -2
- package/src/components/RoomComponents/styled/LastMessagePhoto.tsx +4 -1
- package/src/components/RoomComponents/styled/LastMessageVideo.tsx +4 -1
- package/src/components/styled/AudioMessage.tsx +2 -1
- package/src/components/styled/MessageImage.tsx +45 -6
- package/src/components/styled/VideoMessage.tsx +10 -1
- package/src/context/xmppProvider.tsx +45 -20
- package/src/helpers/initXmppRooms.tsx +5 -1
- package/src/helpers/resolveInitBeforeLoadUser.ts +123 -11
- package/src/helpers/secureFileUrl.ts +103 -0
- package/src/hooks/useChatWrapperInit.ts +3 -3
- package/src/hooks/useFileToken.ts +7 -0
- package/src/hooks/useSendMessage.tsx +28 -13
- package/src/main.ts +22 -1
- package/src/networking/api-requests/auth.api.ts +125 -52
- package/src/networking/api-requests/roomMembers.api.ts +1 -1
- package/src/networking/api-requests/rooms.api.ts +9 -9
- package/src/networking/api-requests/user.api.ts +8 -7
- package/src/networking/apiClient.ts +68 -123
- package/src/networking/authRefresh.ts +435 -0
- package/src/networking/xmppClient.ts +37 -5
- package/src/roomStore/chatSettingsSlice.ts +28 -3
- package/src/types/types.ts +17 -0
|
@@ -28,6 +28,8 @@ import { MODAL_TYPES } from '../../helpers/constants/MODAL_TYPES';
|
|
|
28
28
|
import { RoomMenu } from '../MenuRoom/MenuRoom';
|
|
29
29
|
import { useRoomState } from '../../hooks/useRoomState';
|
|
30
30
|
import { useChatSettingState } from '../../hooks/useChatSettingState';
|
|
31
|
+
import { useFileToken } from '../../hooks/useFileToken';
|
|
32
|
+
import { appendFileToken } from '../../helpers/secureFileUrl';
|
|
31
33
|
import { View, StyleSheet, Text, Keyboard } from 'react-native';
|
|
32
34
|
import { getIconColor } from '../../helpers/getIconColor';
|
|
33
35
|
import { resolveHeaderHeight } from '../../helpers/headerLayout';
|
|
@@ -43,6 +45,7 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|
|
43
45
|
currentRoom,
|
|
44
46
|
handleBackClick,
|
|
45
47
|
}) => {
|
|
48
|
+
const fileToken = useFileToken();
|
|
46
49
|
const dispatch = useDispatch();
|
|
47
50
|
const { client } = useXmppClient();
|
|
48
51
|
|
|
@@ -152,7 +155,7 @@ const ChatHeader: React.FC<ChatHeaderProps> = ({
|
|
|
152
155
|
<ProfileImagePlaceholder
|
|
153
156
|
name={currentRoom?.title || currentRoom?.name}
|
|
154
157
|
size={40}
|
|
155
|
-
icon={currentRoom?.icon}
|
|
158
|
+
icon={appendFileToken(currentRoom?.icon, fileToken)}
|
|
156
159
|
active={!config?.disableChatInfo?.disableChatHeaderMenu}
|
|
157
160
|
click={
|
|
158
161
|
config?.disableChatInfo?.disableChatHeaderMenu
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
setIsLoading,
|
|
24
24
|
setLastViewedTimestamp,
|
|
25
25
|
} from '../../roomStore/roomsSlice';
|
|
26
|
-
import {
|
|
26
|
+
import {refreshAuthTokensQuietly} from '../../networking/authRefresh';
|
|
27
27
|
import RoomList from './RoomList';
|
|
28
28
|
import {StyledLoaderWrapper} from '../styled/StyledComponents';
|
|
29
29
|
import Modal from '../Modals/Modal/Modal';
|
|
@@ -170,6 +170,16 @@ const ChatWrapper: FC<ChatWrapperProps> = ({
|
|
|
170
170
|
);
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
+
// Top-up rotation after the client is up. Deduped inside
|
|
174
|
+
// `refreshAuthTokensQuietly`, so it can no longer race the 401
|
|
175
|
+
// interceptor into a second, parallel rotation — which the backend
|
|
176
|
+
// would read as reuse. Never rejects, hence no `.catch` here.
|
|
177
|
+
const ensureFreshTokens = () => {
|
|
178
|
+
if (config?.refreshTokens?.enabled) {
|
|
179
|
+
refreshAuthTokensQuietly();
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
173
183
|
const initXmmpClient = async () => {
|
|
174
184
|
// Only sync config to redux if we have one — passing `undefined`
|
|
175
185
|
// wipes whatever XmppProvider already set up.
|
|
@@ -252,14 +262,7 @@ const ChatWrapper: FC<ChatWrapperProps> = ({
|
|
|
252
262
|
devPushLog('warn', 'initializeClient failed (legacy path)', err);
|
|
253
263
|
}
|
|
254
264
|
setInited(true);
|
|
255
|
-
|
|
256
|
-
// refresh() is fire-and-forget but rejects on bad
|
|
257
|
-
// refreshToken / network — surface via .catch instead of
|
|
258
|
-
// leaving the rejection unhandled (was bug #4, id:2).
|
|
259
|
-
refresh().catch((err) =>
|
|
260
|
-
console.warn('refresh failed', err)
|
|
261
|
-
);
|
|
262
|
-
}
|
|
265
|
+
ensureFreshTokens();
|
|
263
266
|
} else if (storedClient) {
|
|
264
267
|
devPushLog('rn', 'ChatWrapper: reusing storedClient');
|
|
265
268
|
setClient(storedClient);
|
|
@@ -274,11 +277,7 @@ const ChatWrapper: FC<ChatWrapperProps> = ({
|
|
|
274
277
|
.catch((err: unknown) => console.warn('getRoomsStanza failed', err));
|
|
275
278
|
}
|
|
276
279
|
setInited(true);
|
|
277
|
-
|
|
278
|
-
refresh().catch((err) =>
|
|
279
|
-
console.warn('refresh failed', err)
|
|
280
|
-
);
|
|
281
|
-
}
|
|
280
|
+
ensureFreshTokens();
|
|
282
281
|
} else if (client) {
|
|
283
282
|
devPushLog('rn', 'ChatWrapper: reusing provider client');
|
|
284
283
|
if (!activeRoomJID) {
|
|
@@ -295,11 +294,7 @@ const ChatWrapper: FC<ChatWrapperProps> = ({
|
|
|
295
294
|
console.warn('getChatsPrivateStoreRequestStanza failed', err)
|
|
296
295
|
);
|
|
297
296
|
setInited(true);
|
|
298
|
-
|
|
299
|
-
refresh().catch((err) =>
|
|
300
|
-
console.warn('refresh failed', err)
|
|
301
|
-
);
|
|
302
|
-
}
|
|
297
|
+
ensureFreshTokens();
|
|
303
298
|
}
|
|
304
299
|
|
|
305
300
|
dispatch(setIsLoading({loading: false}));
|
|
@@ -36,6 +36,7 @@ const LoginWrapper: React.FC<LoginWrapperProps> = ({...props}) => {
|
|
|
36
36
|
...authData.data.user,
|
|
37
37
|
token: authData.data.token,
|
|
38
38
|
refreshToken: authData.data.refreshToken,
|
|
39
|
+
fileToken: (authData.data as any).fileToken || '',
|
|
39
40
|
};
|
|
40
41
|
} catch (error) {
|
|
41
42
|
console.error('Login failed:', error);
|
|
@@ -15,6 +15,8 @@ import { deriveDisplayFilename, isLikelyAudio } from '../../helpers/mimeToExtens
|
|
|
15
15
|
import { FileIcon, PlayIcon } from '../../assets/icons';
|
|
16
16
|
import { defaultMediaDims } from '../../helpers/mediaDimensions';
|
|
17
17
|
import { useChatSettingState } from '../../hooks/useChatSettingState';
|
|
18
|
+
import { useFileToken } from '../../hooks/useFileToken';
|
|
19
|
+
import { appendFileToken } from '../../helpers/secureFileUrl';
|
|
18
20
|
|
|
19
21
|
interface MediaMessageProps {
|
|
20
22
|
mimeType?: string;
|
|
@@ -136,22 +138,26 @@ const PendingMediaMessage: React.FC<{
|
|
|
136
138
|
|
|
137
139
|
const MediaMessage: React.FC<MediaMessageProps> = ({
|
|
138
140
|
mimeType,
|
|
139
|
-
location,
|
|
140
|
-
messageText,
|
|
141
|
+
location: rawLocation,
|
|
142
|
+
messageText: rawMessageText,
|
|
141
143
|
message,
|
|
142
144
|
isUser,
|
|
143
145
|
}) => {
|
|
146
|
+
const fileToken = useFileToken();
|
|
147
|
+
const location = appendFileToken(rawLocation, fileToken);
|
|
148
|
+
const messageText = appendFileToken(rawMessageText, fileToken);
|
|
149
|
+
|
|
144
150
|
if (mimeType) {
|
|
145
151
|
const displayName = deriveDisplayFilename({
|
|
146
152
|
fileName: message?.fileName,
|
|
147
153
|
originalName: (message as any)?.originalName,
|
|
148
|
-
url:
|
|
154
|
+
url: rawLocation || rawMessageText,
|
|
149
155
|
mime: mimeType,
|
|
150
156
|
});
|
|
151
157
|
const isAudioPayload = isLikelyAudio(
|
|
152
158
|
mimeType,
|
|
153
159
|
displayName,
|
|
154
|
-
|
|
160
|
+
rawLocation,
|
|
155
161
|
{
|
|
156
162
|
duration: (message as any)?.duration,
|
|
157
163
|
waveForm: (message as any)?.waveForm,
|
|
@@ -174,7 +180,11 @@ const MediaMessage: React.FC<MediaMessageProps> = ({
|
|
|
174
180
|
return (
|
|
175
181
|
<CustomMessageImage
|
|
176
182
|
fileName={displayName}
|
|
183
|
+
// Full-size original: opened by the preview modal.
|
|
177
184
|
fileURL={location || messageText || ''}
|
|
185
|
+
// ...and the thumbnail the bubble actually renders, which is
|
|
186
|
+
// what the web client shows too.
|
|
187
|
+
locationPreview={messageText}
|
|
178
188
|
mimetype={mimeType}
|
|
179
189
|
/>
|
|
180
190
|
);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
+
import { useFileToken } from '../../../hooks/useFileToken';
|
|
3
|
+
import { appendFileToken } from '../../../helpers/secureFileUrl';
|
|
2
4
|
import {
|
|
3
5
|
CenterContainer,
|
|
4
6
|
UserInfo,
|
|
@@ -69,6 +71,7 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
69
71
|
const { client } = useXmppClient();
|
|
70
72
|
const { user: stateUser, config } = useChatSettingState();
|
|
71
73
|
const activeRoom = useSelector((state: RootState) => getActiveRoom(state));
|
|
74
|
+
const fileToken = useFileToken();
|
|
72
75
|
|
|
73
76
|
// Pull live member list from the MUC when the modal opens — REST
|
|
74
77
|
// hydration only fills the *count* (item.participants /
|
|
@@ -295,7 +298,7 @@ const ChatProfileModal: React.FC<ChatProfileModalProps> = ({
|
|
|
295
298
|
<CenterContainer>
|
|
296
299
|
<ProfileImagePlaceholder
|
|
297
300
|
name={activeRoom.title || activeRoom.name}
|
|
298
|
-
icon={activeRoom.icon}
|
|
301
|
+
icon={appendFileToken(activeRoom.icon, fileToken)}
|
|
299
302
|
// Two gates control whether the icon is editable:
|
|
300
303
|
// 1. role — only non-participants could edit before.
|
|
301
304
|
// 2. NEW: `disableChatInfo.disableIconEdit` — hard kill from
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { withFileToken } from '../../../helpers/secureFileUrl';
|
|
2
3
|
import styled from 'styled-components/native';
|
|
3
4
|
import {
|
|
4
5
|
CenterContainer,
|
|
@@ -154,7 +155,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|
|
154
155
|
);
|
|
155
156
|
|
|
156
157
|
const filePath = FileSystem.cacheDirectory + fileName;
|
|
157
|
-
const download = await FileSystem.downloadAsync(activeFile.fileURL, filePath);
|
|
158
|
+
const download = await FileSystem.downloadAsync(withFileToken(activeFile.fileURL), filePath);
|
|
158
159
|
|
|
159
160
|
if (download.status === 200) {
|
|
160
161
|
await MediaLibrary.saveToLibraryAsync(download.uri);
|
|
@@ -179,7 +180,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|
|
179
180
|
activeFile.mimetype
|
|
180
181
|
);
|
|
181
182
|
const filePath = FileSystem.cacheDirectory + fileName;
|
|
182
|
-
const download = await FileSystem.downloadAsync(activeFile.fileURL, filePath);
|
|
183
|
+
const download = await FileSystem.downloadAsync(withFileToken(activeFile.fileURL), filePath);
|
|
183
184
|
|
|
184
185
|
if (download.status !== 200) {
|
|
185
186
|
Alert.alert('Error', 'Failed to save the file.');
|
|
@@ -272,7 +273,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|
|
272
273
|
<FullScreenImage
|
|
273
274
|
source={{
|
|
274
275
|
uri:
|
|
275
|
-
activeFile.fileURL ||
|
|
276
|
+
withFileToken(activeFile.fileURL) ||
|
|
276
277
|
'https://as2.ftcdn.net/v2/jpg/02/51/95/53/1000_F_251955356_FAQH0U1y1TZw3ZcdPGybwUkH90a3VAhb.jpg',
|
|
277
278
|
}}
|
|
278
279
|
resizeMode="contain"
|
|
@@ -280,7 +281,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|
|
280
281
|
/>
|
|
281
282
|
);
|
|
282
283
|
case activeFile.mimetype.startsWith('video/'):
|
|
283
|
-
return <ModalVideo uri={activeFile.fileURL} />;
|
|
284
|
+
return <ModalVideo uri={withFileToken(activeFile.fileURL)} />;
|
|
284
285
|
case activeFile.mimetype.includes('application/octet-stream'):
|
|
285
286
|
return (
|
|
286
287
|
<View
|
|
@@ -296,7 +297,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|
|
296
297
|
Voice message
|
|
297
298
|
</Text>
|
|
298
299
|
<AudioMessage
|
|
299
|
-
src={activeFile.fileURL}
|
|
300
|
+
src={withFileToken(activeFile.fileURL)}
|
|
300
301
|
mimeType={activeFile.mimetype}
|
|
301
302
|
fileName={activeFile.fileName}
|
|
302
303
|
originalName={activeFile.originalName}
|
|
@@ -336,7 +337,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|
|
336
337
|
)}
|
|
337
338
|
</Text>
|
|
338
339
|
<AudioMessage
|
|
339
|
-
src={activeFile.fileURL}
|
|
340
|
+
src={withFileToken(activeFile.fileURL)}
|
|
340
341
|
mimeType={activeFile.mimetype}
|
|
341
342
|
fileName={activeFile.fileName}
|
|
342
343
|
originalName={activeFile.originalName}
|
|
@@ -346,7 +347,7 @@ const FilePreviewModal: React.FC<FilePreviewModalProps> = ({
|
|
|
346
347
|
</View>
|
|
347
348
|
);
|
|
348
349
|
case activeFile.mimetype === 'application/pdf':
|
|
349
|
-
return <PdfViewer pdfUrl={activeFile.fileURL} />;
|
|
350
|
+
return <PdfViewer pdfUrl={withFileToken(activeFile.fileURL)} />;
|
|
350
351
|
default: {
|
|
351
352
|
const displayName = ensureFilenameHasExtension(
|
|
352
353
|
activeFile.fileName,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import { withFileToken } from '../../../helpers/secureFileUrl';
|
|
2
3
|
import { ActivityIndicator, Platform, StyleSheet, Text, View } from 'react-native';
|
|
3
4
|
import { WebView } from 'react-native-webview';
|
|
4
5
|
import * as FileSystem from 'expo-file-system/legacy';
|
|
@@ -25,7 +26,7 @@ const PdfViewer: React.FC<PdfViewerProps> = ({ pdfUrl }) => {
|
|
|
25
26
|
const run = async () => {
|
|
26
27
|
try {
|
|
27
28
|
const target = FileSystem.cacheDirectory + `pdf-${Date.now()}.pdf`;
|
|
28
|
-
const result = await FileSystem.downloadAsync(pdfUrl, target);
|
|
29
|
+
const result = await FileSystem.downloadAsync(withFileToken(pdfUrl), target);
|
|
29
30
|
if (cancelled) {return;}
|
|
30
31
|
setLocalUri(result.uri);
|
|
31
32
|
} catch (e: any) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { useMemo } from 'react';
|
|
2
|
+
import { useFileToken } from '../../hooks/useFileToken';
|
|
3
|
+
import { appendFileToken } from '../../helpers/secureFileUrl';
|
|
2
4
|
import { IConfig, IRoom } from '../../types/types';
|
|
3
5
|
import { ProfileImagePlaceholder } from '../MainComponents/ProfileImagePlaceholder';
|
|
4
6
|
import {
|
|
@@ -26,6 +28,8 @@ const ChatRoomItem: React.FC<ChatRoomItemProps> = ({
|
|
|
26
28
|
isDriver,
|
|
27
29
|
config,
|
|
28
30
|
}) => {
|
|
31
|
+
// Room icons can live on the membership-gated secure-files host.
|
|
32
|
+
const fileToken = useFileToken();
|
|
29
33
|
const displayName = String(chat?.title || chat?.name || '').trim();
|
|
30
34
|
|
|
31
35
|
const lastMessage = useMemo(() => {
|
|
@@ -86,7 +90,11 @@ const ChatRoomItem: React.FC<ChatRoomItemProps> = ({
|
|
|
86
90
|
|
|
87
91
|
return (
|
|
88
92
|
<ChatItem key={index}>
|
|
89
|
-
<ProfileImagePlaceholder
|
|
93
|
+
<ProfileImagePlaceholder
|
|
94
|
+
name={displayName}
|
|
95
|
+
icon={appendFileToken(chat?.icon, fileToken)}
|
|
96
|
+
active={false}
|
|
97
|
+
/>
|
|
90
98
|
<View
|
|
91
99
|
style={{
|
|
92
100
|
flexDirection: 'column',
|
|
@@ -8,6 +8,12 @@ import {
|
|
|
8
8
|
} from './StyledRoomComponents';
|
|
9
9
|
import { FileIcon } from '../../../assets/icons';
|
|
10
10
|
import { Image, View } from 'react-native';
|
|
11
|
+
import { useFileToken } from '../../../hooks/useFileToken';
|
|
12
|
+
import {
|
|
13
|
+
appendFileToken,
|
|
14
|
+
isSecureFileUrl,
|
|
15
|
+
requestFileTokenRecovery,
|
|
16
|
+
} from '../../../helpers/secureFileUrl';
|
|
11
17
|
|
|
12
18
|
interface LastMessageFileProps
|
|
13
19
|
extends Pick<LastMessage, 'user' | 'originalName' | 'locationPreview'> {}
|
|
@@ -21,6 +27,7 @@ const LastMessageFile: FC<LastMessageFileProps> = ({
|
|
|
21
27
|
locationPreview,
|
|
22
28
|
}) => {
|
|
23
29
|
const [imgSrc, setImgSrc] = useState(locationPreview);
|
|
30
|
+
const fileToken = useFileToken();
|
|
24
31
|
|
|
25
32
|
return (
|
|
26
33
|
<LastRoomMessageContainer>
|
|
@@ -34,13 +41,18 @@ const LastMessageFile: FC<LastMessageFileProps> = ({
|
|
|
34
41
|
>
|
|
35
42
|
{imgSrc ? (
|
|
36
43
|
<Image
|
|
37
|
-
source={{ uri: imgSrc }}
|
|
44
|
+
source={{ uri: appendFileToken(imgSrc, fileToken) }}
|
|
38
45
|
style={{
|
|
39
46
|
borderRadius: 16,
|
|
40
47
|
width: 20,
|
|
41
48
|
height: 20,
|
|
42
49
|
}}
|
|
43
|
-
onError={() =>
|
|
50
|
+
onError={() => {
|
|
51
|
+
if (isSecureFileUrl(imgSrc)) {
|
|
52
|
+
requestFileTokenRecovery();
|
|
53
|
+
}
|
|
54
|
+
setImgSrc(fallbackImage);
|
|
55
|
+
}}
|
|
44
56
|
/>
|
|
45
57
|
) : (
|
|
46
58
|
<FileIcon style={{ width: '20px', height: '20px' }} />
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { FC } from 'react';
|
|
2
|
+
import { useFileToken } from '../../../hooks/useFileToken';
|
|
3
|
+
import { appendFileToken } from '../../../helpers/secureFileUrl';
|
|
2
4
|
import styled from 'styled-components/native';
|
|
3
5
|
import { LastMessage } from '../../../types/types';
|
|
4
6
|
import {
|
|
@@ -30,6 +32,7 @@ const LastMessagePhoto: FC<LastMessagePhotoProps> = ({
|
|
|
30
32
|
locationPreview,
|
|
31
33
|
originalName,
|
|
32
34
|
}) => {
|
|
35
|
+
const fileToken = useFileToken();
|
|
33
36
|
return (
|
|
34
37
|
<LastRoomMessageContainer>
|
|
35
38
|
<LastRoomMessageName>{user?.name || ''}:</LastRoomMessageName>
|
|
@@ -42,7 +45,7 @@ const LastMessagePhoto: FC<LastMessagePhotoProps> = ({
|
|
|
42
45
|
>
|
|
43
46
|
<PhotoContainer>
|
|
44
47
|
<ShadeWrapper>
|
|
45
|
-
<LastMessageImg src={locationPreview} />
|
|
48
|
+
<LastMessageImg src={appendFileToken(locationPreview, fileToken)} />
|
|
46
49
|
</ShadeWrapper>
|
|
47
50
|
</PhotoContainer>
|
|
48
51
|
<LastRoomMessageText>{originalName || 'file'}</LastRoomMessageText>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import React, { FC } from 'react';
|
|
2
|
+
import { useFileToken } from '../../../hooks/useFileToken';
|
|
3
|
+
import { appendFileToken } from '../../../helpers/secureFileUrl';
|
|
2
4
|
import styled from 'styled-components/native';
|
|
3
5
|
import { LastMessage } from '../../../types/types';
|
|
4
6
|
import {
|
|
@@ -45,6 +47,7 @@ const LastMessageVideo: FC<LastMessageVideoProps> = ({
|
|
|
45
47
|
location,
|
|
46
48
|
originalName,
|
|
47
49
|
}) => {
|
|
50
|
+
const fileToken = useFileToken();
|
|
48
51
|
return (
|
|
49
52
|
<LastRoomMessageContainer>
|
|
50
53
|
<LastRoomMessageName>{user?.name || ''}:</LastRoomMessageName>
|
|
@@ -57,7 +60,7 @@ const LastMessageVideo: FC<LastMessageVideoProps> = ({
|
|
|
57
60
|
>
|
|
58
61
|
<VideoContainer>
|
|
59
62
|
<ShadeWrapper>
|
|
60
|
-
<Thumbnail src={location} />
|
|
63
|
+
<Thumbnail src={appendFileToken(location, fileToken)} />
|
|
61
64
|
</ShadeWrapper>
|
|
62
65
|
<PlayButton><Text>▶</Text></PlayButton>
|
|
63
66
|
</VideoContainer>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useEffect, useRef, useState } from 'react';
|
|
2
|
+
import { withFileToken } from '../../helpers/secureFileUrl';
|
|
2
3
|
import {
|
|
3
4
|
View,
|
|
4
5
|
TouchableOpacity,
|
|
@@ -425,7 +426,7 @@ const AudioMessage = ({
|
|
|
425
426
|
const partInfo = await FileSystem.getInfoAsync(part);
|
|
426
427
|
if (!partInfo.exists) {
|
|
427
428
|
const download = await withTimeout(
|
|
428
|
-
FileSystem.downloadAsync(src, part),
|
|
429
|
+
FileSystem.downloadAsync(withFileToken(src), part),
|
|
429
430
|
12000,
|
|
430
431
|
'audio_download_timeout'
|
|
431
432
|
);
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
isSecureFileUrl,
|
|
4
|
+
requestFileTokenRecovery,
|
|
5
|
+
} from '../../helpers/secureFileUrl';
|
|
6
|
+
import { pushLog } from '../../utils/devLogger';
|
|
2
7
|
import { Container } from './StyledInputComponents/MediaComponents';
|
|
3
8
|
import { useDispatch } from 'react-redux';
|
|
4
9
|
import {
|
|
@@ -14,11 +19,23 @@ import {
|
|
|
14
19
|
} from '../../helpers/mediaDimensions';
|
|
15
20
|
|
|
16
21
|
interface CustomMessageImageProps {
|
|
22
|
+
/** Full-size file. Opened in the preview modal, NOT rendered inline. */
|
|
17
23
|
fileURL: string;
|
|
18
24
|
fileName: string;
|
|
19
25
|
mimetype: string;
|
|
26
|
+
/**
|
|
27
|
+
* Server-generated thumbnail — what the bubble actually renders, same
|
|
28
|
+
* as the web client. The full-size original can be many megabytes and,
|
|
29
|
+
* for secure uploads, is served from a different (membership-gated)
|
|
30
|
+
* route; rendering it inline is what left the bubbles blank.
|
|
31
|
+
*/
|
|
32
|
+
locationPreview?: string;
|
|
20
33
|
}
|
|
21
34
|
|
|
35
|
+
/** Keep the personal file token out of the logs. */
|
|
36
|
+
const maskToken = (url?: string) =>
|
|
37
|
+
(url || '').replace(/([?&]ft=)[^&]*/i, '$1<redacted>');
|
|
38
|
+
|
|
22
39
|
const FALLBACK_IMAGE =
|
|
23
40
|
'https://as2.ftcdn.net/v2/jpg/02/51/95/53/1000_F_251955356_FAQH0U1y1TZw3ZcdPGybwUkH90a3VAhb.jpg';
|
|
24
41
|
|
|
@@ -26,7 +43,12 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
|
|
|
26
43
|
fileURL,
|
|
27
44
|
fileName,
|
|
28
45
|
mimetype,
|
|
46
|
+
locationPreview,
|
|
29
47
|
}) => {
|
|
48
|
+
// Preview first, original only as a fallback — mirrors the web client,
|
|
49
|
+
// where the <img> src is the preview and `fileURL` is reserved for the
|
|
50
|
+
// modal.
|
|
51
|
+
const displayURL = locationPreview || fileURL;
|
|
30
52
|
const [loading, setLoading] = useState(true);
|
|
31
53
|
// Start with a neutral box; swap to the real aspect ratio once the
|
|
32
54
|
// image's natural dimensions are known so it's shown scaled, not cropped.
|
|
@@ -35,25 +57,31 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
|
|
|
35
57
|
const dispatch = useDispatch();
|
|
36
58
|
|
|
37
59
|
useEffect(() => {
|
|
38
|
-
if (!
|
|
60
|
+
if (!displayURL) {
|
|
39
61
|
return;
|
|
40
62
|
}
|
|
41
63
|
let active = true;
|
|
42
64
|
Image.getSize(
|
|
43
|
-
|
|
65
|
+
displayURL,
|
|
44
66
|
(w, h) => {
|
|
45
67
|
if (active) {
|
|
46
68
|
setDims(fitMediaDimensions(w, h));
|
|
47
69
|
}
|
|
48
70
|
},
|
|
49
|
-
() => {
|
|
50
|
-
|
|
71
|
+
(error) => {
|
|
72
|
+
// Keep the default box — but say WHICH url failed. A blank
|
|
73
|
+
// bubble is otherwise indistinguishable between "bad url",
|
|
74
|
+
// "403 without a token" and "host unreachable".
|
|
75
|
+
pushLog('warn', 'image: getSize failed', {
|
|
76
|
+
url: maskToken(displayURL),
|
|
77
|
+
error: (error as any)?.message || String(error),
|
|
78
|
+
});
|
|
51
79
|
}
|
|
52
80
|
);
|
|
53
81
|
return () => {
|
|
54
82
|
active = false;
|
|
55
83
|
};
|
|
56
|
-
}, [
|
|
84
|
+
}, [displayURL]);
|
|
57
85
|
|
|
58
86
|
const handleOpen = () => {
|
|
59
87
|
dispatch(setActiveFile({ fileName, fileURL, mimetype }));
|
|
@@ -78,7 +106,7 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
|
|
|
78
106
|
</View>
|
|
79
107
|
)}
|
|
80
108
|
<Image
|
|
81
|
-
source={{ uri:
|
|
109
|
+
source={{ uri: displayURL || FALLBACK_IMAGE }}
|
|
82
110
|
style={{
|
|
83
111
|
width: dims.width,
|
|
84
112
|
height: dims.height,
|
|
@@ -86,6 +114,17 @@ const CustomMessageImage: React.FC<CustomMessageImageProps> = ({
|
|
|
86
114
|
backgroundColor: 'rgba(0, 0, 0, 0)',
|
|
87
115
|
}}
|
|
88
116
|
onLoadEnd={() => setLoading(false)}
|
|
117
|
+
onError={(event) => {
|
|
118
|
+
setLoading(false);
|
|
119
|
+
pushLog('warn', 'image: load failed', {
|
|
120
|
+
url: maskToken(displayURL),
|
|
121
|
+
usingPreview: !!locationPreview,
|
|
122
|
+
error: (event as any)?.nativeEvent?.error,
|
|
123
|
+
});
|
|
124
|
+
if (isSecureFileUrl(displayURL)) {
|
|
125
|
+
requestFileTokenRecovery();
|
|
126
|
+
}
|
|
127
|
+
}}
|
|
89
128
|
// The box already matches the natural aspect ratio, so 'cover'
|
|
90
129
|
// fills it edge-to-edge with no actual cropping or letterboxing.
|
|
91
130
|
resizeMode="cover"
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import React, { useEffect, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
isSecureFileUrl,
|
|
4
|
+
requestFileTokenRecovery,
|
|
5
|
+
} from '../../helpers/secureFileUrl';
|
|
2
6
|
import { Image, Pressable, StyleSheet, View } from 'react-native';
|
|
3
7
|
import { useDispatch } from 'react-redux';
|
|
4
8
|
import {
|
|
@@ -139,7 +143,12 @@ const CustomMessageVideo: React.FC<CustomMessageVideoProps> = ({
|
|
|
139
143
|
<PosterVideo
|
|
140
144
|
previewURL={previewURL}
|
|
141
145
|
onOpen={handleOpen}
|
|
142
|
-
onPosterError={() =>
|
|
146
|
+
onPosterError={() => {
|
|
147
|
+
if (isSecureFileUrl(previewURL)) {
|
|
148
|
+
requestFileTokenRecovery();
|
|
149
|
+
}
|
|
150
|
+
setPosterFailed(true);
|
|
151
|
+
}}
|
|
143
152
|
/>
|
|
144
153
|
);
|
|
145
154
|
}
|
|
@@ -14,7 +14,7 @@ import { VideoCallOverlay } from '../components/VideoCalls/VideoCallOverlay';
|
|
|
14
14
|
import XmppClient, {
|
|
15
15
|
XmppCredentialsProvider,
|
|
16
16
|
} from '../networking/xmppClient';
|
|
17
|
-
import {
|
|
17
|
+
import { refreshAuthTokensQuietly } from '../networking/authRefresh';
|
|
18
18
|
import {
|
|
19
19
|
IConfig,
|
|
20
20
|
ProviderBootstrapStatus,
|
|
@@ -107,23 +107,25 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
|
|
|
107
107
|
// the XmppClient at least doesn't crash on `undefined.password`.
|
|
108
108
|
const credentialsProvider = useMemo<XmppCredentialsProvider>(() => {
|
|
109
109
|
return async () => {
|
|
110
|
-
// 1.
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
110
|
+
// 1. Make sure the REST tokens are fresh before re-minting XMPP
|
|
111
|
+
// creds. This used to call `config.refreshTokens.refreshFunction`
|
|
112
|
+
// directly and then fall into step 2, which refreshes again —
|
|
113
|
+
// two rotations back to back, which the new backend scheme
|
|
114
|
+
// reads as a race at best and token reuse at worst. One call to
|
|
115
|
+
// the shared rotation point covers both the consumer-supplied
|
|
116
|
+
// function and the built-in endpoint.
|
|
117
|
+
if (config?.refreshTokens?.enabled) {
|
|
118
|
+
const rotated = await refreshAuthTokensQuietly();
|
|
119
|
+
|
|
120
|
+
if (rotated?.xmppPassword) {
|
|
121
|
+
const rotatedUser = store.getState().chatSettingStore.user;
|
|
122
|
+
return {
|
|
123
|
+
username:
|
|
124
|
+
rotatedUser?.xmppUsername ||
|
|
125
|
+
rotatedUser?.defaultWallet?.walletAddress ||
|
|
126
|
+
'',
|
|
127
|
+
password: rotated.xmppPassword,
|
|
128
|
+
};
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
|
|
@@ -163,7 +165,7 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
|
|
|
163
165
|
}, [
|
|
164
166
|
config?.jwtLogin?.enabled,
|
|
165
167
|
config?.jwtLogin?.token,
|
|
166
|
-
config?.refreshTokens?.
|
|
168
|
+
config?.refreshTokens?.enabled,
|
|
167
169
|
config?.userLogin?.enabled,
|
|
168
170
|
config?.customLogin?.enabled,
|
|
169
171
|
config?.initBeforeLoadAuth?.myEndpoint,
|
|
@@ -353,6 +355,29 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
|
|
|
353
355
|
store.dispatch(setConfig(config));
|
|
354
356
|
}
|
|
355
357
|
|
|
358
|
+
// Rotate BEFORE the first XMPP connect. On the rotating backend
|
|
359
|
+
// the xmpp password is a short-lived JWT that only the newest
|
|
360
|
+
// rotation knows, and each rotation invalidates its predecessors
|
|
361
|
+
// — so a snapshot's password (a host's cached `userLogin.user`,
|
|
362
|
+
// a persisted session) is usually already dead at boot. Without
|
|
363
|
+
// this, the first SASL attempt fails `not-authorized`, all three
|
|
364
|
+
// bootstrap retries re-present the same dead password, and the
|
|
365
|
+
// user dead-ends on the Connection error modal even though one
|
|
366
|
+
// rotation away a perfectly good session existed. The rotation
|
|
367
|
+
// also lands the fileToken pre-render, so secure media never
|
|
368
|
+
// paints blank first. Quiet + deduped; when it can't rotate
|
|
369
|
+
// (no refresh material, non-rotating backend) we just connect
|
|
370
|
+
// with what the snapshot has, exactly as before.
|
|
371
|
+
// jwtLogin is skipped: /users/client just minted fresh creds.
|
|
372
|
+
let connectPassword = resolved.xmppPassword;
|
|
373
|
+
if (config?.refreshTokens?.enabled && !config?.jwtLogin?.enabled) {
|
|
374
|
+
const rotated = await refreshAuthTokensQuietly();
|
|
375
|
+
if (cancelled) {return;}
|
|
376
|
+
if (rotated?.xmppPassword) {
|
|
377
|
+
connectPassword = rotated.xmppPassword;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
356
381
|
// Kick off REST /chats/my; allRoomPresences below needs the
|
|
357
382
|
// room list to be in redux, otherwise it joins 0 MUCs and the
|
|
358
383
|
// user receives zero realtime messages until they manually tap
|
|
@@ -362,7 +387,7 @@ export const XmppProvider: React.FC<XmppProviderProps> = ({ children, config, is
|
|
|
362
387
|
|
|
363
388
|
const c = await initializeClient(
|
|
364
389
|
resolved.xmppUsername!,
|
|
365
|
-
|
|
390
|
+
connectPassword,
|
|
366
391
|
config?.xmppSettings
|
|
367
392
|
);
|
|
368
393
|
if (cancelled) {return;}
|
|
@@ -4,6 +4,7 @@ import { initRoomsPresence } from './initRoomsPresence';
|
|
|
4
4
|
import XmppClient from '../networking/xmppClient';
|
|
5
5
|
import { IConfig, IRoom, User } from '../types/types';
|
|
6
6
|
import { store } from '../roomStore';
|
|
7
|
+
import { refreshAuthTokensQuietly } from '../networking/authRefresh';
|
|
7
8
|
|
|
8
9
|
const initXmppRooms = async (
|
|
9
10
|
user: User,
|
|
@@ -59,7 +60,10 @@ const initXmppRooms = async (
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
if (config?.refreshTokens?.enabled) {
|
|
62
|
-
|
|
63
|
+
// Was a bare call to the consumer-supplied refreshFunction, i.e. a
|
|
64
|
+
// rotation completely outside the SDK's lock. Routed through the
|
|
65
|
+
// one rotation point, which calls that same function internally.
|
|
66
|
+
refreshAuthTokensQuietly();
|
|
63
67
|
}
|
|
64
68
|
} catch (error) {
|
|
65
69
|
console.error(error);
|