@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
|
@@ -18,8 +18,9 @@ export async function loginEmail(email: string, password: string) {
|
|
|
18
18
|
user: User;
|
|
19
19
|
refreshToken: string;
|
|
20
20
|
token: string;
|
|
21
|
+
fileToken?: string;
|
|
21
22
|
}>(
|
|
22
|
-
'/users/login-with-email',
|
|
23
|
+
'/v1/users/login-with-email',
|
|
23
24
|
{
|
|
24
25
|
email,
|
|
25
26
|
password,
|
|
@@ -29,6 +30,16 @@ export async function loginEmail(email: string, password: string) {
|
|
|
29
30
|
|
|
30
31
|
console.log('loginEmail res', res.data);
|
|
31
32
|
|
|
33
|
+
// The fileToken arrives at the TOP level of the login response, next to
|
|
34
|
+
// token/refreshToken — not inside `user`. Folding it in here is what
|
|
35
|
+
// makes secure media (`secure-files.*`, uploaded via /v2/files/secure)
|
|
36
|
+
// render at all: without it every such image 403s and the bubble comes
|
|
37
|
+
// up blank.
|
|
38
|
+
res.data.user = {
|
|
39
|
+
...res.data.user,
|
|
40
|
+
fileToken: res.data.fileToken || '',
|
|
41
|
+
};
|
|
42
|
+
|
|
32
43
|
return res;
|
|
33
44
|
}
|
|
34
45
|
|
|
@@ -39,7 +50,7 @@ export function loginSocial(
|
|
|
39
50
|
authToken: string = 'authToken'
|
|
40
51
|
) {
|
|
41
52
|
return http.post<any>(
|
|
42
|
-
'/users/login',
|
|
53
|
+
'/v1/users/login',
|
|
43
54
|
{
|
|
44
55
|
idToken,
|
|
45
56
|
accessToken,
|
|
@@ -58,7 +69,7 @@ export function registerSocial(
|
|
|
58
69
|
signUpPlan?: string
|
|
59
70
|
) {
|
|
60
71
|
return http.post(
|
|
61
|
-
'/users',
|
|
72
|
+
'/v1/users',
|
|
62
73
|
{
|
|
63
74
|
idToken,
|
|
64
75
|
accessToken,
|
|
@@ -72,7 +83,7 @@ export function registerSocial(
|
|
|
72
83
|
|
|
73
84
|
export function checkEmailExist(email: string) {
|
|
74
85
|
return http.get(
|
|
75
|
-
'/users/checkEmail/' + email,
|
|
86
|
+
'/v1/users/checkEmail/' + email,
|
|
76
87
|
|
|
77
88
|
{ headers: { Authorization: getCurrentAppToken() } }
|
|
78
89
|
);
|
|
@@ -84,7 +95,8 @@ export async function loginViaJwt(clientToken: string): Promise<User> {
|
|
|
84
95
|
user: User;
|
|
85
96
|
refreshToken: string;
|
|
86
97
|
token: string;
|
|
87
|
-
|
|
98
|
+
fileToken?: string;
|
|
99
|
+
}>('/v1/users/client', null, { headers: { 'x-custom-token': clientToken } });
|
|
88
100
|
|
|
89
101
|
console.log('✅ loginViaJwt: Received response', {
|
|
90
102
|
hasUser: !!response.data.user,
|
|
@@ -103,6 +115,8 @@ export async function loginViaJwt(clientToken: string): Promise<User> {
|
|
|
103
115
|
...response.data.user,
|
|
104
116
|
refreshToken: response.data.refreshToken,
|
|
105
117
|
token: response.data.token,
|
|
118
|
+
// Top-level, same as loginEmail — see the note there.
|
|
119
|
+
fileToken: response.data.fileToken || '',
|
|
106
120
|
};
|
|
107
121
|
|
|
108
122
|
return user;
|
|
@@ -131,7 +145,7 @@ export async function loginViaJwt(clientToken: string): Promise<User> {
|
|
|
131
145
|
|
|
132
146
|
export function uploadFile(formData: FormData) {
|
|
133
147
|
const token = store.getState().chatSettingStore?.user?.token ?? '';
|
|
134
|
-
return http.post('/files/', formData, {
|
|
148
|
+
return http.post('/v1/files/', formData, {
|
|
135
149
|
headers: {
|
|
136
150
|
Authorization: token,
|
|
137
151
|
Accept: '*/*',
|
|
@@ -144,79 +158,91 @@ export function uploadFile(formData: FormData) {
|
|
|
144
158
|
});
|
|
145
159
|
}
|
|
146
160
|
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
161
|
+
const SECURE_UPLOAD_PATH = '/v2/files/secure';
|
|
162
|
+
const LEGACY_UPLOAD_PATH = '/v1/files/';
|
|
163
|
+
|
|
164
|
+
const cloneFormData = (formData: FormData): FormData => {
|
|
165
|
+
const copy = new FormData();
|
|
166
|
+
const source = formData as any;
|
|
167
|
+
|
|
168
|
+
if (Array.isArray(source?._parts)) {
|
|
169
|
+
for (const [key, value] of source._parts) {
|
|
170
|
+
copy.append(key, value);
|
|
171
|
+
}
|
|
172
|
+
return copy;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (typeof source?.forEach === 'function') {
|
|
176
|
+
source.forEach((value: any, key: string) => copy.append(key, value));
|
|
177
|
+
return copy;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (typeof source?.getParts === 'function') {
|
|
181
|
+
for (const part of source.getParts()) {
|
|
182
|
+
// `headers` is derived by getParts() and must not be copied back —
|
|
183
|
+
// RN regenerates it from the value.
|
|
184
|
+
const { fieldName, string, headers: _headers, ...file } = part;
|
|
185
|
+
copy.append(fieldName, string !== undefined ? string : file);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return copy;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
function postMultipart(
|
|
193
|
+
url: string,
|
|
194
|
+
body: FormData,
|
|
195
|
+
path: string
|
|
196
|
+
): Promise<{ data: any }> {
|
|
176
197
|
const token = store.getState().chatSettingStore?.user?.token ?? '';
|
|
177
|
-
const baseUrl = getCurrentBaseURL();
|
|
178
|
-
const url = `${baseUrl.replace(/\/$/, '')}${UPLOAD_PATH}`;
|
|
179
198
|
|
|
180
199
|
pushLog('http', `→ POST ${url} (xhr)`, {
|
|
181
|
-
|
|
200
|
+
path,
|
|
201
|
+
baseUrl: getCurrentBaseURL() || '[empty]',
|
|
202
|
+
headers: {
|
|
203
|
+
Authorization: token ? token.slice(0, 16) + '…' : '[empty]',
|
|
204
|
+
Accept: '*/*',
|
|
205
|
+
},
|
|
182
206
|
});
|
|
183
207
|
|
|
184
208
|
return new Promise((resolve, reject) => {
|
|
185
209
|
const xhr = new XMLHttpRequest();
|
|
186
210
|
|
|
187
211
|
const failNetwork = (reason: string) => {
|
|
188
|
-
pushLog('http', `← ERR xhr ${
|
|
212
|
+
pushLog('http', `← ERR xhr ${path}`, { reason, url });
|
|
189
213
|
const err: any = new Error(reason);
|
|
190
214
|
err.code = 'ERR_NETWORK';
|
|
191
|
-
err.config = { url:
|
|
215
|
+
err.config = { url: path };
|
|
192
216
|
reject(err);
|
|
193
217
|
};
|
|
194
218
|
|
|
195
219
|
xhr.onload = () => {
|
|
196
220
|
const status = xhr.status;
|
|
197
|
-
let
|
|
221
|
+
let responseBody: any = null;
|
|
198
222
|
try {
|
|
199
|
-
|
|
223
|
+
responseBody = JSON.parse(xhr.responseText);
|
|
200
224
|
} catch {
|
|
201
|
-
|
|
225
|
+
responseBody = xhr.responseText || null;
|
|
202
226
|
}
|
|
203
227
|
|
|
204
228
|
if (status < 200 || status >= 300) {
|
|
205
|
-
pushLog('http', `← ${status} POST ${
|
|
229
|
+
pushLog('http', `← ${status} POST ${path} (xhr)`, responseBody);
|
|
206
230
|
const err: any = new Error(`Upload failed: ${status}`);
|
|
207
231
|
// Shaped like an axios error so callers' `err.response.status`
|
|
208
232
|
// checks keep working — notably useSendMessage's retry with the
|
|
209
233
|
// singular "file" field on 500.
|
|
210
|
-
err.response = { status, statusText: xhr.statusText, data:
|
|
211
|
-
err.config = { url:
|
|
234
|
+
err.response = { status, statusText: xhr.statusText, data: responseBody };
|
|
235
|
+
err.config = { url: path };
|
|
212
236
|
reject(err);
|
|
213
237
|
return;
|
|
214
238
|
}
|
|
215
239
|
|
|
216
|
-
pushLog('http', `← ${status} POST ${
|
|
217
|
-
resultsCount: Array.isArray(
|
|
240
|
+
pushLog('http', `← ${status} POST ${path} (xhr)`, {
|
|
241
|
+
resultsCount: Array.isArray(responseBody?.results)
|
|
242
|
+
? responseBody.results.length
|
|
243
|
+
: 'n/a',
|
|
218
244
|
});
|
|
219
|
-
resolve({ data:
|
|
245
|
+
resolve({ data: responseBody });
|
|
220
246
|
};
|
|
221
247
|
|
|
222
248
|
xhr.onerror = () => failNetwork('Network request failed');
|
|
@@ -226,8 +252,55 @@ export function uploadFileMultipart(formData: FormData): Promise<{ data: any }>
|
|
|
226
252
|
xhr.open('POST', url);
|
|
227
253
|
xhr.setRequestHeader('Authorization', token);
|
|
228
254
|
xhr.setRequestHeader('Accept', '*/*');
|
|
229
|
-
// Deliberately no Content-Type — see the note above
|
|
230
|
-
|
|
231
|
-
xhr.send(formData as any);
|
|
255
|
+
// Deliberately no Content-Type — see the note above.
|
|
256
|
+
xhr.send(body as any);
|
|
232
257
|
});
|
|
233
258
|
}
|
|
259
|
+
|
|
260
|
+
const canFallBackToLegacyUpload = (error: unknown): boolean => {
|
|
261
|
+
const status = (error as { response?: { status?: number } })?.response
|
|
262
|
+
?.status;
|
|
263
|
+
if (typeof status !== 'number') {return true;}
|
|
264
|
+
return status !== 401 && status !== 413;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
let secureUploadUnavailable = false;
|
|
268
|
+
|
|
269
|
+
export async function uploadFileV2(
|
|
270
|
+
formData: FormData,
|
|
271
|
+
activeRoomJID: string
|
|
272
|
+
): Promise<{ data: any }> {
|
|
273
|
+
const chatName = (activeRoomJID || '').split('@')[0];
|
|
274
|
+
const apiRoot = getCurrentBaseURL().replace(/\/$/, '');
|
|
275
|
+
|
|
276
|
+
if (!secureUploadUnavailable && chatName) {
|
|
277
|
+
const body = cloneFormData(formData);
|
|
278
|
+
body.append('chatName', chatName);
|
|
279
|
+
|
|
280
|
+
try {
|
|
281
|
+
return await postMultipart(
|
|
282
|
+
`${apiRoot}${SECURE_UPLOAD_PATH}`,
|
|
283
|
+
body,
|
|
284
|
+
SECURE_UPLOAD_PATH
|
|
285
|
+
);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (!canFallBackToLegacyUpload(error)) {throw error;}
|
|
288
|
+
|
|
289
|
+
secureUploadUnavailable = true;
|
|
290
|
+
console.warn(
|
|
291
|
+
`[chat] ${SECURE_UPLOAD_PATH} unreachable, falling back to ${LEGACY_UPLOAD_PATH} for this session`,
|
|
292
|
+
error
|
|
293
|
+
);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return postMultipart(
|
|
298
|
+
`${apiRoot}${LEGACY_UPLOAD_PATH}`,
|
|
299
|
+
formData,
|
|
300
|
+
LEGACY_UPLOAD_PATH
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export const __resetSecureUploadLatchForTests = (): void => {
|
|
305
|
+
secureUploadUnavailable = false;
|
|
306
|
+
};
|
|
@@ -181,7 +181,7 @@ export async function getRooms(): Promise<{ items: ApiRoom[] }> {
|
|
|
181
181
|
|
|
182
182
|
getRoomsInFlightToken = token;
|
|
183
183
|
getRoomsInFlight = (async () => {
|
|
184
|
-
const response = await http.get('/chats/my', {
|
|
184
|
+
const response = await http.get('/v1/chats/my', {
|
|
185
185
|
headers: { Authorization: token },
|
|
186
186
|
});
|
|
187
187
|
lastGetRoomsResponse = response.data;
|
|
@@ -206,7 +206,7 @@ export async function getRoomByName(chatName: string): Promise<ApiRoom> {
|
|
|
206
206
|
const token = store.getState().chatSettingStore.user.token || '';
|
|
207
207
|
|
|
208
208
|
try {
|
|
209
|
-
const response = await http.get(`/chats/my/${chatName}`, {
|
|
209
|
+
const response = await http.get(`/v1/chats/my/${chatName}`, {
|
|
210
210
|
headers: {
|
|
211
211
|
Authorization: token,
|
|
212
212
|
},
|
|
@@ -221,7 +221,7 @@ export async function postRoom(data: PostRoom) {
|
|
|
221
221
|
const token = store.getState().chatSettingStore.user.token || '';
|
|
222
222
|
|
|
223
223
|
try {
|
|
224
|
-
const response = await http.post('/chats', data, {
|
|
224
|
+
const response = await http.post('/v1/chats', data, {
|
|
225
225
|
headers: {
|
|
226
226
|
Authorization: token,
|
|
227
227
|
},
|
|
@@ -240,7 +240,7 @@ export async function postPrivateRoom(
|
|
|
240
240
|
|
|
241
241
|
try {
|
|
242
242
|
const response = await http.post(
|
|
243
|
-
'/chats/private',
|
|
243
|
+
'/v1/chats/private',
|
|
244
244
|
{ username },
|
|
245
245
|
{
|
|
246
246
|
headers: {
|
|
@@ -260,7 +260,7 @@ export async function postReportRoom(data: PostReportRoom) {
|
|
|
260
260
|
|
|
261
261
|
try {
|
|
262
262
|
const response = await http.post(
|
|
263
|
-
`/chats/reports/${chatName}`,
|
|
263
|
+
`/v1/chats/reports/${chatName}`,
|
|
264
264
|
{ category, text },
|
|
265
265
|
{
|
|
266
266
|
headers: {
|
|
@@ -282,7 +282,7 @@ export async function postAddRoomMember(
|
|
|
282
282
|
|
|
283
283
|
try {
|
|
284
284
|
const response = await http.post(
|
|
285
|
-
`/chats/users-access`,
|
|
285
|
+
`/v1/chats/users-access`,
|
|
286
286
|
{ chatName, members },
|
|
287
287
|
{
|
|
288
288
|
headers: {
|
|
@@ -301,7 +301,7 @@ export async function deleteRoomMember(data: DeleteRoomMember) {
|
|
|
301
301
|
const token = store.getState().chatSettingStore.user.token || '';
|
|
302
302
|
|
|
303
303
|
try {
|
|
304
|
-
const response = await http.delete(`/chats/users-access`, {
|
|
304
|
+
const response = await http.delete(`/v1/chats/users-access`, {
|
|
305
305
|
headers: {
|
|
306
306
|
Authorization: token,
|
|
307
307
|
},
|
|
@@ -320,7 +320,7 @@ export async function deleteRoom(name: string) {
|
|
|
320
320
|
const token = store.getState().chatSettingStore.user.token || '';
|
|
321
321
|
|
|
322
322
|
try {
|
|
323
|
-
const response = await http.delete('/chats', {
|
|
323
|
+
const response = await http.delete('/v1/chats', {
|
|
324
324
|
headers: {
|
|
325
325
|
Authorization: token,
|
|
326
326
|
},
|
|
@@ -360,7 +360,7 @@ export async function createChatCall(
|
|
|
360
360
|
|
|
361
361
|
try {
|
|
362
362
|
await http.post(
|
|
363
|
-
`/chats/call/create/${chatName}`,
|
|
363
|
+
`/v1/chats/call/create/${chatName}`,
|
|
364
364
|
{ kind },
|
|
365
365
|
{
|
|
366
366
|
headers: {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import http from '../apiClient';
|
|
2
|
+
import { normalizeApiPath } from '../apiClient';
|
|
2
3
|
import { store } from '../../roomStore';
|
|
3
4
|
import { User } from '../../types/types';
|
|
4
5
|
|
|
@@ -10,7 +11,7 @@ interface GetMyUserOptions {
|
|
|
10
11
|
export async function getMyUser(options?: GetMyUserOptions): Promise<User> {
|
|
11
12
|
const token =
|
|
12
13
|
options?.token || store.getState().chatSettingStore.user.token || '';
|
|
13
|
-
const endpoint = options?.endpoint || '/users/my';
|
|
14
|
+
const endpoint = normalizeApiPath(options?.endpoint) || '/v1/users/my';
|
|
14
15
|
|
|
15
16
|
const response = await http.get(endpoint, {
|
|
16
17
|
headers: { Authorization: token },
|
|
@@ -24,7 +25,7 @@ export async function getMyUser(options?: GetMyUserOptions): Promise<User> {
|
|
|
24
25
|
|
|
25
26
|
export function getDocuments(walletAddress: string) {
|
|
26
27
|
const token = store.getState().chatSettingStore.user.token || '';
|
|
27
|
-
return http.get(`/docs/${walletAddress}`, {
|
|
28
|
+
return http.get(`/v1/docs/${walletAddress}`, {
|
|
28
29
|
headers: {
|
|
29
30
|
Authorization: `Bearer ${token}`,
|
|
30
31
|
},
|
|
@@ -33,7 +34,7 @@ export function getDocuments(walletAddress: string) {
|
|
|
33
34
|
|
|
34
35
|
export function getExportMyData() {
|
|
35
36
|
const token = store.getState().chatSettingStore.user.token || '';
|
|
36
|
-
return http.get('/users/exportData', {
|
|
37
|
+
return http.get('/v1/users/exportData', {
|
|
37
38
|
headers: {
|
|
38
39
|
Authorization: token,
|
|
39
40
|
responseType: 'arraybuffer',
|
|
@@ -43,7 +44,7 @@ export function getExportMyData() {
|
|
|
43
44
|
|
|
44
45
|
export function deleteDocument(fileId: string) {
|
|
45
46
|
const token = store.getState().chatSettingStore.user.token || '';
|
|
46
|
-
return http.delete(`/files/${fileId}`, {
|
|
47
|
+
return http.delete(`/v1/files/${fileId}`, {
|
|
47
48
|
headers: {
|
|
48
49
|
Authorization: token,
|
|
49
50
|
},
|
|
@@ -51,18 +52,18 @@ export function deleteDocument(fileId: string) {
|
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
export function deleteMe() {
|
|
54
|
-
return http.delete('/users');
|
|
55
|
+
return http.delete('/v1/users');
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export function updateMe(data: any) {
|
|
58
|
-
return http.put('/users', data);
|
|
59
|
+
return http.put('/v1/users', data);
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
export async function updateProfile(fd: FormData): Promise<{ user: User }> {
|
|
62
63
|
const token = store.getState().chatSettingStore.user.token || '';
|
|
63
64
|
|
|
64
65
|
try {
|
|
65
|
-
const response = await http.put('/users', fd, {
|
|
66
|
+
const response = await http.put('/v1/users', fd, {
|
|
66
67
|
headers: {
|
|
67
68
|
Authorization: token,
|
|
68
69
|
'Content-Type': 'multipart/form-data',
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import { store } from '../roomStore';
|
|
3
3
|
|
|
4
|
-
import { logout
|
|
4
|
+
import { logout } from '../roomStore/chatSettingsSlice';
|
|
5
5
|
import { installAxiosCapture } from '../utils/devLogger';
|
|
6
|
+
import {
|
|
7
|
+
refreshAuthTokens,
|
|
8
|
+
isRefreshFatalError,
|
|
9
|
+
RefreshResult,
|
|
10
|
+
} from './authRefresh';
|
|
6
11
|
|
|
7
12
|
// Per product-code-policy: no compiled-in Ethora endpoints or tokens.
|
|
8
13
|
// Consumers must call `setBaseURL(baseUrl, appToken)` (or pass
|
|
@@ -21,10 +26,21 @@ const http = axios.create({
|
|
|
21
26
|
// `http` instance.
|
|
22
27
|
installAxiosCapture(http);
|
|
23
28
|
|
|
29
|
+
const stripApiVersion = (baseUrl: string): string =>
|
|
30
|
+
baseUrl.replace(/\/+$/, '').replace(/\/v\d+$/, '');
|
|
31
|
+
|
|
32
|
+
export const normalizeApiPath = (path?: string): string | undefined => {
|
|
33
|
+
if (!path) {return path;}
|
|
34
|
+
if (/^https?:\/\//i.test(path)) {return path;}
|
|
35
|
+
if (/^\/v\d+\//.test(path)) {return path;}
|
|
36
|
+
return `/v1${path.startsWith('/') ? '' : '/'}${path}`;
|
|
37
|
+
};
|
|
38
|
+
|
|
24
39
|
export function setBaseURL(baseUrl?: string, appToken?: string) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
const normalized = baseUrl ? stripApiVersion(baseUrl) : baseUrl;
|
|
41
|
+
if (normalized && normalized !== currentBaseURL) {
|
|
42
|
+
currentBaseURL = normalized;
|
|
43
|
+
http.defaults.baseURL = normalized;
|
|
28
44
|
}
|
|
29
45
|
if (appToken && appToken !== currentAppToken) {
|
|
30
46
|
currentAppToken = appToken;
|
|
@@ -39,149 +55,78 @@ export function getCurrentBaseURL() {
|
|
|
39
55
|
return currentBaseURL;
|
|
40
56
|
}
|
|
41
57
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
)
|
|
54
|
-
.then((response) => {
|
|
55
|
-
store.dispatch(
|
|
56
|
-
refreshTokens({
|
|
57
|
-
token: response.data.token,
|
|
58
|
-
refreshToken: response.data.refreshToken,
|
|
59
|
-
})
|
|
60
|
-
);
|
|
61
|
-
// BUGFIX: previously the `.then` only dispatched and never
|
|
62
|
-
// resolved, so every `await refresh()` callsite hung forever
|
|
63
|
-
// on the happy path. Resolve with the axios response shape so
|
|
64
|
-
// the interceptor can read `tokens.data.token` to retry.
|
|
65
|
-
resolve(response);
|
|
66
|
-
})
|
|
67
|
-
.catch((error) => {
|
|
68
|
-
reject(error);
|
|
69
|
-
});
|
|
70
|
-
} catch (error) {
|
|
71
|
-
// BUGFIX: previously the outer-try catch dispatched logout but
|
|
72
|
-
// never rejected, so a synchronous throw from `http.post` (e.g.
|
|
73
|
-
// a bad URL) would hang the promise. Reject explicitly.
|
|
74
|
-
console.log('errr');
|
|
75
|
-
store.dispatch(logout());
|
|
76
|
-
reject(error);
|
|
77
|
-
}
|
|
78
|
-
});
|
|
58
|
+
/**
|
|
59
|
+
* @deprecated Import `refreshAuthTokens` from `./authRefresh` instead.
|
|
60
|
+
*
|
|
61
|
+
* Kept as a thin forwarder so existing call sites keep working while
|
|
62
|
+
* they migrate. Declared as a function (not `const refresh =
|
|
63
|
+
* refreshAuthTokens`) on purpose: this module and `authRefresh` import
|
|
64
|
+
* each other, and a const would capture the binding before the other
|
|
65
|
+
* module finished evaluating.
|
|
66
|
+
*/
|
|
67
|
+
export function refresh(): Promise<RefreshResult> {
|
|
68
|
+
return refreshAuthTokens();
|
|
79
69
|
}
|
|
80
70
|
|
|
81
|
-
let isRefreshing = false;
|
|
82
|
-
let failedQueue: any[] = [];
|
|
83
|
-
|
|
84
|
-
const addRequestToQueue = (config: any) => {
|
|
85
|
-
return new Promise((resolve, reject) => {
|
|
86
|
-
failedQueue.push({ resolve, reject, config });
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
const processQueue = (newAccessToken: string) => {
|
|
91
|
-
for (const request of failedQueue) {
|
|
92
|
-
if (newAccessToken) {
|
|
93
|
-
request.config.headers.Authorization = newAccessToken;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
request.resolve(http(request.config));
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
failedQueue = [];
|
|
100
|
-
};
|
|
101
|
-
|
|
102
71
|
http.interceptors.response.use(
|
|
103
72
|
(response) => response,
|
|
104
73
|
async (error) => {
|
|
105
|
-
// BUGFIX: the original condition was `if (!enabled)` — inverted, so
|
|
106
|
-
// turning `refreshTokens.enabled: true` actually DISABLED the refresh
|
|
107
|
-
// path on 401. Use the correct sense.
|
|
108
|
-
if (!store.getState().chatSettingStore?.config?.refreshTokens?.enabled) {
|
|
109
|
-
// BUGFIX: previously this branch fell off the end of the async
|
|
110
|
-
// function and returned `undefined`, which axios treats as a
|
|
111
|
-
// resolved-with-undefined response. Callers expecting an error
|
|
112
|
-
// got `undefined` instead, silently swallowing every failure
|
|
113
|
-
// when the refresh path isn't configured. Re-reject so the
|
|
114
|
-
// original error reaches the caller.
|
|
115
|
-
return Promise.reject(error);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
74
|
const originalRequest = error.config;
|
|
119
75
|
const status = error.response?.status;
|
|
120
76
|
|
|
121
77
|
// Refresh paths only fire on 401. Anything else passes through.
|
|
122
|
-
if (!error.response || status !== 401) {
|
|
78
|
+
if (!error.response || status !== 401 || !originalRequest) {
|
|
123
79
|
return Promise.reject(error);
|
|
124
80
|
}
|
|
125
81
|
|
|
82
|
+
// Never retry twice, and never touch the auth endpoints themselves —
|
|
83
|
+
// both would recurse. (`_retry` was set but never checked before,
|
|
84
|
+
// so a request could bounce between 401 and replay indefinitely.)
|
|
126
85
|
if (
|
|
127
|
-
|
|
86
|
+
originalRequest._retry ||
|
|
87
|
+
originalRequest.url === '/v1/users/login/refresh' ||
|
|
88
|
+
originalRequest.url === '/v1/users/login'
|
|
128
89
|
) {
|
|
129
|
-
|
|
130
|
-
// tokens, retry the original request.
|
|
131
|
-
try {
|
|
132
|
-
const fn = store.getState().chatSettingStore?.config?.refreshTokens?.refreshFunction;
|
|
133
|
-
if (!fn) return Promise.reject(error);
|
|
134
|
-
const result = await fn();
|
|
135
|
-
const { refreshToken: newRefreshToken, accessToken } = result || { refreshToken: '', accessToken: '' };
|
|
136
|
-
store.dispatch(
|
|
137
|
-
refreshTokens({
|
|
138
|
-
token: accessToken,
|
|
139
|
-
refreshToken: newRefreshToken || '',
|
|
140
|
-
})
|
|
141
|
-
);
|
|
142
|
-
// BUGFIX: previously this branch returned `undefined` after
|
|
143
|
-
// dispatching, so the original request was never retried and
|
|
144
|
-
// the caller resolved with undefined. Stamp the new token on
|
|
145
|
-
// the original config and replay.
|
|
146
|
-
if (originalRequest?.headers) {
|
|
147
|
-
originalRequest.headers.Authorization = accessToken;
|
|
148
|
-
}
|
|
149
|
-
return http(originalRequest);
|
|
150
|
-
} catch (refreshErr) {
|
|
151
|
-
return Promise.reject(refreshErr);
|
|
152
|
-
}
|
|
90
|
+
return Promise.reject(error);
|
|
153
91
|
}
|
|
154
92
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
) {
|
|
93
|
+
const refreshConfig =
|
|
94
|
+
store.getState().chatSettingStore?.config?.refreshTokens;
|
|
95
|
+
|
|
96
|
+
// BUGFIX (kept): the original condition here was inverted, so
|
|
97
|
+
// `refreshTokens.enabled: true` actually DISABLED the refresh path.
|
|
98
|
+
if (!refreshConfig?.enabled) {
|
|
161
99
|
return Promise.reject(error);
|
|
162
100
|
}
|
|
163
101
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
102
|
+
// Nothing to rotate with — reject rather than firing a doomed
|
|
103
|
+
// refresh that would look like a bad-token event to the backend.
|
|
104
|
+
const hasRefreshableSession = Boolean(
|
|
105
|
+
refreshConfig.refreshFunction ||
|
|
106
|
+
store.getState().chatSettingStore.user?.refreshToken
|
|
107
|
+
);
|
|
108
|
+
if (!hasRefreshableSession) {
|
|
109
|
+
return Promise.reject(error);
|
|
169
110
|
}
|
|
170
111
|
|
|
171
|
-
|
|
112
|
+
originalRequest._retry = true;
|
|
113
|
+
|
|
172
114
|
try {
|
|
173
|
-
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
originalRequest.headers
|
|
178
|
-
|
|
115
|
+
// One shared rotation for every 401 in flight — `refreshAuthTokens`
|
|
116
|
+
// dedupes internally, which replaces the old isRefreshing/
|
|
117
|
+
// failedQueue machinery entirely.
|
|
118
|
+
const tokens = await refreshAuthTokens();
|
|
119
|
+
originalRequest.headers = originalRequest.headers || {};
|
|
120
|
+
originalRequest.headers.Authorization = tokens.token;
|
|
179
121
|
return http(originalRequest);
|
|
180
122
|
} catch (refreshErr) {
|
|
181
|
-
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
123
|
+
// Only a genuinely dead session logs the user out. A network
|
|
124
|
+
// blip, a 5xx, or a REFRESH_IN_PROGRESS race must leave the
|
|
125
|
+
// session alone — logging out on those is exactly the mass-logout
|
|
126
|
+
// failure mode the new backend scheme would otherwise cause.
|
|
127
|
+
if (isRefreshFatalError(refreshErr)) {
|
|
128
|
+
store.dispatch(logout());
|
|
129
|
+
}
|
|
185
130
|
return Promise.reject(refreshErr);
|
|
186
131
|
}
|
|
187
132
|
}
|