@ethora/chat-component-rn 26.5.5 → 26.5.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/lib/commonjs/components/MainComponents/ChatHeader.js +7 -1
- package/lib/commonjs/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatRoom.js +8 -2
- package/lib/commonjs/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/commonjs/components/MainComponents/ChatWrapper.js +2 -1
- package/lib/commonjs/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MediaMessage.js +11 -5
- package/lib/commonjs/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/commonjs/components/MainComponents/MessageList.js +10 -2
- package/lib/commonjs/components/MainComponents/MessageList.js.map +1 -1
- package/lib/commonjs/components/MessageBubble/Message.js +60 -7
- package/lib/commonjs/components/MessageBubble/Message.js.map +1 -1
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js +35 -16
- package/lib/commonjs/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js +11 -2
- package/lib/commonjs/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js +1 -1
- package/lib/commonjs/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/commonjs/components/styled/MediaFilePreview.js +96 -5
- package/lib/commonjs/components/styled/MediaFilePreview.js.map +1 -1
- package/lib/commonjs/components/styled/MessageImage.js +1 -2
- package/lib/commonjs/components/styled/MessageImage.js.map +1 -1
- package/lib/commonjs/components/styled/SendInput.js +4 -2
- package/lib/commonjs/components/styled/SendInput.js.map +1 -1
- package/lib/commonjs/components/styled/VideoMessage.js +2 -10
- package/lib/commonjs/components/styled/VideoMessage.js.map +1 -1
- package/lib/commonjs/context/xmppProvider.js +71 -2
- package/lib/commonjs/context/xmppProvider.js.map +1 -1
- package/lib/commonjs/helpers/mimeToExtension.js +131 -0
- package/lib/commonjs/helpers/mimeToExtension.js.map +1 -0
- package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js +73 -1
- package/lib/commonjs/helpers/resolveInitBeforeLoadUser.js.map +1 -1
- package/lib/commonjs/hooks/useChatWrapperInit.js +22 -10
- package/lib/commonjs/hooks/useChatWrapperInit.js.map +1 -1
- package/lib/commonjs/hooks/useEventHandlers.js +9 -1
- package/lib/commonjs/hooks/useEventHandlers.js.map +1 -1
- package/lib/commonjs/hooks/useMessageHeapState.js +6 -2
- package/lib/commonjs/hooks/useMessageHeapState.js.map +1 -1
- package/lib/commonjs/hooks/useSendMessage.js +48 -4
- package/lib/commonjs/hooks/useSendMessage.js.map +1 -1
- package/lib/commonjs/networking/api-requests/auth.api.js +7 -1
- package/lib/commonjs/networking/api-requests/auth.api.js.map +1 -1
- package/lib/commonjs/networking/xmppClient.js +87 -1
- package/lib/commonjs/networking/xmppClient.js.map +1 -1
- package/lib/commonjs/roomStore/roomHeapSlice.js +26 -8
- package/lib/commonjs/roomStore/roomHeapSlice.js.map +1 -1
- package/lib/module/components/MainComponents/ChatHeader.js +7 -1
- package/lib/module/components/MainComponents/ChatHeader.js.map +1 -1
- package/lib/module/components/MainComponents/ChatRoom.js +8 -2
- package/lib/module/components/MainComponents/ChatRoom.js.map +1 -1
- package/lib/module/components/MainComponents/ChatWrapper.js +2 -1
- package/lib/module/components/MainComponents/ChatWrapper.js.map +1 -1
- package/lib/module/components/MainComponents/MediaMessage.js +11 -5
- package/lib/module/components/MainComponents/MediaMessage.js.map +1 -1
- package/lib/module/components/MainComponents/MessageList.js +11 -3
- package/lib/module/components/MainComponents/MessageList.js.map +1 -1
- package/lib/module/components/MessageBubble/Message.js +60 -7
- package/lib/module/components/MessageBubble/Message.js.map +1 -1
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js +36 -17
- package/lib/module/components/Modals/FilePreviewModal/FilePreviewModal.js.map +1 -1
- package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js +11 -2
- package/lib/module/components/Modals/ModalWrapper/ModalWrapper.js.map +1 -1
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js +1 -1
- package/lib/module/components/Modals/UserProfileModal/UserProfileModal.js.map +1 -1
- package/lib/module/components/styled/MediaFilePreview.js +97 -6
- package/lib/module/components/styled/MediaFilePreview.js.map +1 -1
- package/lib/module/components/styled/MessageImage.js +1 -2
- package/lib/module/components/styled/MessageImage.js.map +1 -1
- package/lib/module/components/styled/SendInput.js +4 -2
- package/lib/module/components/styled/SendInput.js.map +1 -1
- package/lib/module/components/styled/VideoMessage.js +2 -10
- package/lib/module/components/styled/VideoMessage.js.map +1 -1
- package/lib/module/context/xmppProvider.js +73 -3
- package/lib/module/context/xmppProvider.js.map +1 -1
- package/lib/module/helpers/mimeToExtension.js +124 -0
- package/lib/module/helpers/mimeToExtension.js.map +1 -0
- package/lib/module/helpers/resolveInitBeforeLoadUser.js +71 -0
- package/lib/module/helpers/resolveInitBeforeLoadUser.js.map +1 -1
- package/lib/module/hooks/useChatWrapperInit.js +22 -10
- package/lib/module/hooks/useChatWrapperInit.js.map +1 -1
- package/lib/module/hooks/useEventHandlers.js +9 -1
- package/lib/module/hooks/useEventHandlers.js.map +1 -1
- package/lib/module/hooks/useMessageHeapState.js +6 -2
- package/lib/module/hooks/useMessageHeapState.js.map +1 -1
- package/lib/module/hooks/useSendMessage.js +49 -5
- package/lib/module/hooks/useSendMessage.js.map +1 -1
- package/lib/module/networking/api-requests/auth.api.js +7 -1
- package/lib/module/networking/api-requests/auth.api.js.map +1 -1
- package/lib/module/networking/xmppClient.js +87 -1
- package/lib/module/networking/xmppClient.js.map +1 -1
- package/lib/module/roomStore/roomHeapSlice.js +24 -7
- package/lib/module/roomStore/roomHeapSlice.js.map +1 -1
- package/lib/typescript/components/MainComponents/ChatHeader.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatRoom.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/ChatWrapper.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MediaMessage.d.ts.map +1 -1
- package/lib/typescript/components/MainComponents/MessageList.d.ts.map +1 -1
- package/lib/typescript/components/MessageBubble/Message.d.ts.map +1 -1
- package/lib/typescript/components/Modals/FilePreviewModal/FilePreviewModal.d.ts.map +1 -1
- package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts +6 -0
- package/lib/typescript/components/Modals/ModalWrapper/ModalWrapper.d.ts.map +1 -1
- package/lib/typescript/components/styled/MediaFilePreview.d.ts.map +1 -1
- package/lib/typescript/components/styled/MessageImage.d.ts.map +1 -1
- package/lib/typescript/components/styled/SendInput.d.ts.map +1 -1
- package/lib/typescript/context/xmppProvider.d.ts.map +1 -1
- package/lib/typescript/helpers/mimeToExtension.d.ts +37 -0
- package/lib/typescript/helpers/mimeToExtension.d.ts.map +1 -0
- package/lib/typescript/helpers/resolveInitBeforeLoadUser.d.ts +18 -0
- package/lib/typescript/helpers/resolveInitBeforeLoadUser.d.ts.map +1 -1
- package/lib/typescript/hooks/useChatWrapperInit.d.ts.map +1 -1
- package/lib/typescript/hooks/useEventHandlers.d.ts +6 -0
- package/lib/typescript/hooks/useEventHandlers.d.ts.map +1 -1
- package/lib/typescript/hooks/useMessageHeapState.d.ts +2 -0
- package/lib/typescript/hooks/useMessageHeapState.d.ts.map +1 -1
- package/lib/typescript/hooks/useSendMessage.d.ts +2 -1
- package/lib/typescript/hooks/useSendMessage.d.ts.map +1 -1
- package/lib/typescript/networking/api-requests/auth.api.d.ts.map +1 -1
- package/lib/typescript/networking/xmppClient.d.ts +25 -1
- package/lib/typescript/networking/xmppClient.d.ts.map +1 -1
- package/lib/typescript/roomStore/roomHeapSlice.d.ts +22 -1
- package/lib/typescript/roomStore/roomHeapSlice.d.ts.map +1 -1
- package/lib/typescript/types/types.d.ts +6 -0
- package/lib/typescript/types/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/MainComponents/ChatHeader.tsx +5 -1
- package/src/components/MainComponents/ChatRoom.tsx +6 -1
- package/src/components/MainComponents/ChatWrapper.tsx +1 -0
- package/src/components/MainComponents/MediaMessage.tsx +15 -8
- package/src/components/MainComponents/MessageList.tsx +10 -3
- package/src/components/MessageBubble/Message.tsx +64 -10
- package/src/components/Modals/FilePreviewModal/FilePreviewModal.tsx +30 -12
- package/src/components/Modals/ModalWrapper/ModalWrapper.tsx +22 -1
- package/src/components/Modals/UserProfileModal/UserProfileModal.tsx +1 -1
- package/src/components/styled/MediaFilePreview.tsx +91 -5
- package/src/components/styled/MessageImage.tsx +1 -2
- package/src/components/styled/SendInput.tsx +3 -0
- package/src/components/styled/VideoMessage.tsx +3 -10
- package/src/context/xmppProvider.tsx +90 -2
- package/src/helpers/mimeToExtension.ts +135 -0
- package/src/helpers/resolveInitBeforeLoadUser.ts +79 -0
- package/src/hooks/useChatWrapperInit.ts +51 -46
- package/src/hooks/useEventHandlers.tsx +24 -1
- package/src/hooks/useMessageHeapState.tsx +6 -2
- package/src/hooks/useSendMessage.tsx +75 -4
- package/src/networking/api-requests/auth.api.ts +5 -1
- package/src/networking/xmppClient.ts +95 -1
- package/src/roomStore/roomHeapSlice.ts +47 -4
- package/src/types/types.ts +10 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// MIME → file extension table for download/preview filename fallbacks.
|
|
2
|
+
// Centralized so FilePreviewModal, MediaMessage, and any future media
|
|
3
|
+
// surface share one source of truth. Keep additions sorted by category.
|
|
4
|
+
|
|
5
|
+
const MIME_TO_EXT: Record<string, string> = {
|
|
6
|
+
// images
|
|
7
|
+
'image/jpeg': '.jpg',
|
|
8
|
+
'image/jpg': '.jpg',
|
|
9
|
+
'image/png': '.png',
|
|
10
|
+
'image/gif': '.gif',
|
|
11
|
+
'image/webp': '.webp',
|
|
12
|
+
'image/heic': '.heic',
|
|
13
|
+
'image/heif': '.heif',
|
|
14
|
+
'image/bmp': '.bmp',
|
|
15
|
+
'image/svg+xml': '.svg',
|
|
16
|
+
'image/tiff': '.tiff',
|
|
17
|
+
|
|
18
|
+
// video
|
|
19
|
+
'video/mp4': '.mp4',
|
|
20
|
+
'video/quicktime': '.mov',
|
|
21
|
+
'video/x-m4v': '.m4v',
|
|
22
|
+
'video/webm': '.webm',
|
|
23
|
+
'video/3gpp': '.3gp',
|
|
24
|
+
'video/x-matroska': '.mkv',
|
|
25
|
+
'video/mpeg': '.mpeg',
|
|
26
|
+
|
|
27
|
+
// audio
|
|
28
|
+
'audio/mpeg': '.mp3',
|
|
29
|
+
'audio/mp3': '.mp3',
|
|
30
|
+
'audio/mp4': '.m4a',
|
|
31
|
+
'audio/x-m4a': '.m4a',
|
|
32
|
+
'audio/wav': '.wav',
|
|
33
|
+
'audio/x-wav': '.wav',
|
|
34
|
+
'audio/aac': '.aac',
|
|
35
|
+
'audio/ogg': '.ogg',
|
|
36
|
+
'audio/flac': '.flac',
|
|
37
|
+
'audio/webm': '.weba',
|
|
38
|
+
|
|
39
|
+
// documents
|
|
40
|
+
'application/pdf': '.pdf',
|
|
41
|
+
'application/msword': '.doc',
|
|
42
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
|
|
43
|
+
'.docx',
|
|
44
|
+
'application/vnd.ms-excel': '.xls',
|
|
45
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '.xlsx',
|
|
46
|
+
'application/vnd.ms-powerpoint': '.ppt',
|
|
47
|
+
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
|
|
48
|
+
'.pptx',
|
|
49
|
+
'text/plain': '.txt',
|
|
50
|
+
'text/csv': '.csv',
|
|
51
|
+
'text/html': '.html',
|
|
52
|
+
'application/json': '.json',
|
|
53
|
+
'application/xml': '.xml',
|
|
54
|
+
'text/xml': '.xml',
|
|
55
|
+
'application/rtf': '.rtf',
|
|
56
|
+
|
|
57
|
+
// archives
|
|
58
|
+
'application/zip': '.zip',
|
|
59
|
+
'application/x-rar-compressed': '.rar',
|
|
60
|
+
'application/x-7z-compressed': '.7z',
|
|
61
|
+
'application/gzip': '.gz',
|
|
62
|
+
'application/x-tar': '.tar',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const VALID_EXT_RE = /\.[a-z0-9]{1,8}$/i;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Returns the conventional extension (including the leading dot) for a
|
|
69
|
+
* MIME type, or `.bin` as a last-resort fallback so callers never end up
|
|
70
|
+
* with an extension-less filename.
|
|
71
|
+
*/
|
|
72
|
+
export function getExtensionForMime(mime: string | undefined | null): string {
|
|
73
|
+
if (!mime) {return '.bin';}
|
|
74
|
+
const normalized = mime.toLowerCase().split(';')[0]!.trim();
|
|
75
|
+
return MIME_TO_EXT[normalized] || '.bin';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Parses a URL or file path and returns the last segment, URL-decoded,
|
|
80
|
+
* stripped of query/hash. Returns `''` when nothing usable is present.
|
|
81
|
+
*/
|
|
82
|
+
export function filenameFromUrl(url: string | undefined | null): string {
|
|
83
|
+
if (!url) {return '';}
|
|
84
|
+
try {
|
|
85
|
+
// Strip query + hash without depending on a real URL parser (some RN
|
|
86
|
+
// URLs from CDNs do not parse cleanly).
|
|
87
|
+
const noQuery = url.split('?')[0]!.split('#')[0]!;
|
|
88
|
+
const last = noQuery.split('/').pop() || '';
|
|
89
|
+
return decodeURIComponent(last);
|
|
90
|
+
} catch {
|
|
91
|
+
return '';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Ensures `fileName` ends with a valid extension. Strategy:
|
|
97
|
+
* 1. If the input already has an extension, return it unchanged.
|
|
98
|
+
* 2. Otherwise, derive the extension from `mime` and append it.
|
|
99
|
+
* 3. If `mime` is unknown, append `.bin`.
|
|
100
|
+
*
|
|
101
|
+
* Empty/falsy filenames get a `media_<timestamp>` base before the
|
|
102
|
+
* extension is appended so callers always get a usable filename.
|
|
103
|
+
*/
|
|
104
|
+
export function ensureFilenameHasExtension(
|
|
105
|
+
fileName: string | undefined | null,
|
|
106
|
+
mime: string | undefined | null
|
|
107
|
+
): string {
|
|
108
|
+
const base =
|
|
109
|
+
(fileName && fileName.trim()) || `media_${Date.now()}`;
|
|
110
|
+
if (VALID_EXT_RE.test(base)) {return base;}
|
|
111
|
+
return `${base}${getExtensionForMime(mime)}`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Best-effort filename derivation for a message bubble:
|
|
116
|
+
* - server-provided `fileName`
|
|
117
|
+
* - server-provided `originalName`
|
|
118
|
+
* - URL pathname
|
|
119
|
+
* - generic `media_<timestamp>` so we never render an empty label
|
|
120
|
+
*
|
|
121
|
+
* Always returns a string with a valid extension.
|
|
122
|
+
*/
|
|
123
|
+
export function deriveDisplayFilename(opts: {
|
|
124
|
+
fileName?: string | null;
|
|
125
|
+
originalName?: string | null;
|
|
126
|
+
url?: string | null;
|
|
127
|
+
mime?: string | null;
|
|
128
|
+
}): string {
|
|
129
|
+
const candidate =
|
|
130
|
+
(opts.fileName && opts.fileName.trim()) ||
|
|
131
|
+
(opts.originalName && opts.originalName.trim()) ||
|
|
132
|
+
filenameFromUrl(opts.url) ||
|
|
133
|
+
'';
|
|
134
|
+
return ensureFilenameHasExtension(candidate, opts.mime);
|
|
135
|
+
}
|
|
@@ -237,4 +237,83 @@ export const applyResolvedUserToStore = (user?: User | null) => {
|
|
|
237
237
|
store.dispatch(setUser(user));
|
|
238
238
|
};
|
|
239
239
|
|
|
240
|
+
/**
|
|
241
|
+
* Re-mints a User with a fresh xmppPassword for the XMPP credentials-
|
|
242
|
+
* provider path (bug #17). Unlike `resolveInitBeforeLoadUser` this
|
|
243
|
+
* ALWAYS hydrates via the chosen auth path even if the cached user
|
|
244
|
+
* already had xmppCredentials — that's the whole point, the old ones
|
|
245
|
+
* are stale.
|
|
246
|
+
*
|
|
247
|
+
* Selection order:
|
|
248
|
+
* 1. `jwtLogin` — re-exchange the JWT via `/users/client`.
|
|
249
|
+
* 2. Anything else (`userLogin` / `customLogin` / store / persisted)
|
|
250
|
+
* — `tryHydrateViaMy` on the current user, which calls
|
|
251
|
+
* `/users/my` and, on 401, refreshes the access token via
|
|
252
|
+
* `/users/login/refresh` and retries.
|
|
253
|
+
*
|
|
254
|
+
* Returns `null` only when nothing usable is available — the caller
|
|
255
|
+
* (xmppProvider) falls back to the cached creds in that case.
|
|
256
|
+
*/
|
|
257
|
+
export const refreshUserCredentialsForXmpp = async (
|
|
258
|
+
config?: IConfig,
|
|
259
|
+
signal?: AbortSignal
|
|
260
|
+
): Promise<User | null> => {
|
|
261
|
+
if (signal?.aborted) {return null;}
|
|
262
|
+
|
|
263
|
+
// Priority 1: jwtLogin — same path as `resolveInitBeforeLoadUser`
|
|
264
|
+
// priority 3. We always re-exchange because the original JWT may
|
|
265
|
+
// itself have expired (and the consumer is expected to swap
|
|
266
|
+
// `config.jwtLogin.token` for a fresh one before then).
|
|
267
|
+
if (config?.jwtLogin?.enabled && config?.jwtLogin?.token) {
|
|
268
|
+
try {
|
|
269
|
+
const fresh = await loginViaJwt(config.jwtLogin.token);
|
|
270
|
+
const normalized = normalizeUserForXmpp(fresh);
|
|
271
|
+
if (normalized && hasXmppCredentials(normalized)) {return normalized;}
|
|
272
|
+
} catch (error) {
|
|
273
|
+
if (signal?.aborted || isAbortError(error)) {return null;}
|
|
274
|
+
// fall through to /users/my path
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const myEndpoint = config?.initBeforeLoadAuth?.myEndpoint || '/users/my';
|
|
279
|
+
|
|
280
|
+
// Priority 2: redux store user (covers userLogin + customLogin +
|
|
281
|
+
// anything previously persisted into chatSettingStore.user). The
|
|
282
|
+
// user must have at least a refreshToken for the refresh chain to
|
|
283
|
+
// produce something useful.
|
|
284
|
+
const currentUser = store.getState().chatSettingStore.user;
|
|
285
|
+
if (currentUser?.token || currentUser?.refreshToken) {
|
|
286
|
+
const hydrated = await tryHydrateViaMy(
|
|
287
|
+
currentUser as User,
|
|
288
|
+
myEndpoint,
|
|
289
|
+
signal
|
|
290
|
+
).catch(() => null);
|
|
291
|
+
if (hydrated && hasXmppCredentials(hydrated)) {return hydrated;}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Priority 3: persisted user in AsyncStorage — defensive; the store
|
|
295
|
+
// should already mirror this, but if the redux state got cleared
|
|
296
|
+
// mid-session (logout race) this gives us one last shot.
|
|
297
|
+
try {
|
|
298
|
+
const storedUser = await asyncLocalStorage<User>(
|
|
299
|
+
localStorageConstants.ETHORA_USER
|
|
300
|
+
).get();
|
|
301
|
+
if (
|
|
302
|
+
storedUser &&
|
|
303
|
+
(storedUser.token || storedUser.refreshToken || storedUser.xmppPassword)
|
|
304
|
+
) {
|
|
305
|
+
const hydrated = await tryHydrateViaMy(
|
|
306
|
+
storedUser,
|
|
307
|
+
myEndpoint,
|
|
308
|
+
signal
|
|
309
|
+
).catch(() => null);
|
|
310
|
+
if (hydrated && hasXmppCredentials(hydrated)) {return hydrated;}
|
|
311
|
+
}
|
|
312
|
+
} catch {
|
|
313
|
+
// ignore storage errors
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return null;
|
|
317
|
+
};
|
|
318
|
+
|
|
240
319
|
export default resolveInitBeforeLoadUser;
|
|
@@ -119,10 +119,8 @@ const useChatWrapperInit = ({
|
|
|
119
119
|
user.xmppUsername || user?.defaultWallet?.walletAddress,
|
|
120
120
|
user?.xmppPassword,
|
|
121
121
|
config?.xmppSettings
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
return client;
|
|
125
|
-
});
|
|
122
|
+
);
|
|
123
|
+
console.log(' Client initialized', newClient?.status);
|
|
126
124
|
|
|
127
125
|
if (roomsList && Object.keys(roomsList).length > 0) {
|
|
128
126
|
console.log(
|
|
@@ -156,27 +154,29 @@ const useChatWrapperInit = ({
|
|
|
156
154
|
await newClient.getRoomsStanza();
|
|
157
155
|
}
|
|
158
156
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
console.log(
|
|
166
|
-
'Got chats private store',
|
|
167
|
-
roomTimestampObject?.length || 0
|
|
168
|
-
);
|
|
169
|
-
updatedChatLastTimestamps(roomTimestampObject, dispatch);
|
|
170
|
-
if (!hasSyncedHistoryRef.current) {
|
|
171
|
-
console.log('Updating messages till last');
|
|
172
|
-
await updateMessagesTillLast(rooms, newClient);
|
|
173
|
-
hasSyncedHistoryRef.current = true;
|
|
174
|
-
}
|
|
175
|
-
setClient(newClient);
|
|
176
|
-
setConnectionLost(false);
|
|
177
|
-
console.log('Initialization complete');
|
|
178
|
-
}
|
|
157
|
+
try {
|
|
158
|
+
const roomTimestampObject: any =
|
|
159
|
+
await newClient.getChatsPrivateStoreRequestStanza();
|
|
160
|
+
console.log(
|
|
161
|
+
'Got chats private store',
|
|
162
|
+
roomTimestampObject?.length || 0
|
|
179
163
|
);
|
|
164
|
+
if (roomTimestampObject) {
|
|
165
|
+
updatedChatLastTimestamps(roomTimestampObject, dispatch);
|
|
166
|
+
}
|
|
167
|
+
if (!hasSyncedHistoryRef.current) {
|
|
168
|
+
console.log('Updating messages till last');
|
|
169
|
+
await updateMessagesTillLast(rooms, newClient);
|
|
170
|
+
hasSyncedHistoryRef.current = true;
|
|
171
|
+
}
|
|
172
|
+
setClient(newClient);
|
|
173
|
+
setConnectionLost(false);
|
|
174
|
+
console.log('Initialization complete');
|
|
175
|
+
} catch (err) {
|
|
176
|
+
console.warn('privateStore/timestamps failed', err);
|
|
177
|
+
setClient(newClient);
|
|
178
|
+
setConnectionLost(false);
|
|
179
|
+
}
|
|
180
180
|
|
|
181
181
|
{
|
|
182
182
|
config?.refreshTokens?.enabled && refresh();
|
|
@@ -204,29 +204,34 @@ const useChatWrapperInit = ({
|
|
|
204
204
|
}
|
|
205
205
|
setInited(true);
|
|
206
206
|
console.log(' Getting chats private store for existing client');
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
console.log(
|
|
214
|
-
'Got chats private store for existing client',
|
|
215
|
-
roomTimestampObject?.length || 0
|
|
216
|
-
);
|
|
217
|
-
updatedChatLastTimestamps(roomTimestampObject, dispatch);
|
|
218
|
-
if (!hasSyncedHistoryRef.current) {
|
|
219
|
-
console.log(
|
|
220
|
-
'Updating messages till last for existing client'
|
|
221
|
-
);
|
|
222
|
-
await updateMessagesTillLast(rooms, client);
|
|
223
|
-
hasSyncedHistoryRef.current = true;
|
|
224
|
-
}
|
|
225
|
-
setClient(client);
|
|
226
|
-
setConnectionLost(false);
|
|
227
|
-
console.log(' Initialization complete for existing client');
|
|
228
|
-
}
|
|
207
|
+
try {
|
|
208
|
+
const roomTimestampObject: any =
|
|
209
|
+
await client.getChatsPrivateStoreRequestStanza();
|
|
210
|
+
console.log(
|
|
211
|
+
'Got chats private store for existing client',
|
|
212
|
+
roomTimestampObject?.length || 0
|
|
229
213
|
);
|
|
214
|
+
if (roomTimestampObject) {
|
|
215
|
+
updatedChatLastTimestamps(roomTimestampObject, dispatch);
|
|
216
|
+
}
|
|
217
|
+
if (!hasSyncedHistoryRef.current) {
|
|
218
|
+
console.log(
|
|
219
|
+
'Updating messages till last for existing client'
|
|
220
|
+
);
|
|
221
|
+
await updateMessagesTillLast(rooms, client);
|
|
222
|
+
hasSyncedHistoryRef.current = true;
|
|
223
|
+
}
|
|
224
|
+
setClient(client);
|
|
225
|
+
setConnectionLost(false);
|
|
226
|
+
console.log(' Initialization complete for existing client');
|
|
227
|
+
} catch (err) {
|
|
228
|
+
console.warn(
|
|
229
|
+
'privateStore/timestamps failed (existing client)',
|
|
230
|
+
err
|
|
231
|
+
);
|
|
232
|
+
setClient(client);
|
|
233
|
+
setConnectionLost(false);
|
|
234
|
+
}
|
|
230
235
|
{
|
|
231
236
|
config?.refreshTokens?.enabled && refresh();
|
|
232
237
|
}
|
|
@@ -23,10 +23,17 @@ interface MessageEditedEvent {
|
|
|
23
23
|
user: any;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
interface MessageRetryEvent {
|
|
27
|
+
messageId: string;
|
|
28
|
+
roomJID: string;
|
|
29
|
+
messageType: 'text' | 'media';
|
|
30
|
+
}
|
|
31
|
+
|
|
26
32
|
export interface EventHandlersHook {
|
|
27
33
|
handleMessageSent: (event: MessageSentEvent) => Promise<void>;
|
|
28
34
|
handleMessageFailed: (event: MessageFailedEvent) => void;
|
|
29
35
|
handleMessageEdited: (event: MessageEditedEvent) => void;
|
|
36
|
+
handleMessageRetry: (event: MessageRetryEvent) => void;
|
|
30
37
|
}
|
|
31
38
|
|
|
32
39
|
export const useEventHandlers = (config?: IConfig): EventHandlersHook => {
|
|
@@ -77,5 +84,21 @@ export const useEventHandlers = (config?: IConfig): EventHandlersHook => {
|
|
|
77
84
|
[config]
|
|
78
85
|
);
|
|
79
86
|
|
|
80
|
-
|
|
87
|
+
const handleMessageRetry = useCallback(
|
|
88
|
+
(event: MessageRetryEvent) => {
|
|
89
|
+
try {
|
|
90
|
+
config?.eventHandlers?.onMessageRetry?.(event);
|
|
91
|
+
} catch (error) {
|
|
92
|
+
console.error('Error in message retry handler:', error);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
[config]
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
handleMessageSent,
|
|
100
|
+
handleMessageFailed,
|
|
101
|
+
handleMessageEdited,
|
|
102
|
+
handleMessageRetry,
|
|
103
|
+
};
|
|
81
104
|
};
|
|
@@ -3,9 +3,13 @@ import { RootState } from '../roomStore';
|
|
|
3
3
|
|
|
4
4
|
export const useMessageHeapState = () => {
|
|
5
5
|
const queue = useSelector(
|
|
6
|
-
(state: RootState) => state.roomHeapSlice
|
|
6
|
+
(state: RootState) => state.roomHeapSlice?.messageHeap
|
|
7
|
+
);
|
|
8
|
+
const failedMessages = useSelector(
|
|
9
|
+
(state: RootState) => state.roomHeapSlice?.failedMessages
|
|
7
10
|
);
|
|
8
11
|
const idSet = new Set(queue?.map((m) => m.id) ?? []);
|
|
12
|
+
const failedIdSet = new Set(Object.keys(failedMessages || {}));
|
|
9
13
|
|
|
10
|
-
return { queue, idSet };
|
|
14
|
+
return { queue, idSet, failedMessages, failedIdSet };
|
|
11
15
|
};
|
|
@@ -3,7 +3,11 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
3
3
|
import { useXmppClient } from '../context/xmppProvider';
|
|
4
4
|
import { useDispatch, useSelector } from 'react-redux';
|
|
5
5
|
import { addRoomMessage, setEditAction } from '../roomStore/roomsSlice';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
addMessageToHeap,
|
|
8
|
+
clearMessageFailure,
|
|
9
|
+
markMessageFailed,
|
|
10
|
+
} from '../roomStore/roomHeapSlice';
|
|
7
11
|
import { uploadFile } from '../networking/api-requests/auth.api';
|
|
8
12
|
import { RootState } from '../roomStore';
|
|
9
13
|
import { useEventHandlers } from './useEventHandlers';
|
|
@@ -38,8 +42,13 @@ export const useSendMessage = (_configOverride?: IConfig) => {
|
|
|
38
42
|
handleMessageSent,
|
|
39
43
|
handleMessageFailed,
|
|
40
44
|
handleMessageEdited,
|
|
45
|
+
handleMessageRetry,
|
|
41
46
|
} = useEventHandlers(config);
|
|
42
47
|
|
|
48
|
+
const failedMessages = useSelector(
|
|
49
|
+
(state: RootState) => state.roomHeapSlice?.failedMessages || {}
|
|
50
|
+
);
|
|
51
|
+
|
|
43
52
|
const sendMessage = useCallback(
|
|
44
53
|
async (
|
|
45
54
|
message: string,
|
|
@@ -140,6 +149,17 @@ export const useSendMessage = (_configOverride?: IConfig) => {
|
|
|
140
149
|
messageType: 'text',
|
|
141
150
|
});
|
|
142
151
|
} catch (error) {
|
|
152
|
+
dispatch(
|
|
153
|
+
markMessageFailed({
|
|
154
|
+
kind: 'text',
|
|
155
|
+
id: optimisticId,
|
|
156
|
+
roomJID: activeRoomJID,
|
|
157
|
+
body: message,
|
|
158
|
+
isReply,
|
|
159
|
+
isChecked,
|
|
160
|
+
mainMessage,
|
|
161
|
+
})
|
|
162
|
+
);
|
|
143
163
|
handleMessageFailed({
|
|
144
164
|
message,
|
|
145
165
|
roomJID: activeRoomJID,
|
|
@@ -158,11 +178,12 @@ export const useSendMessage = (_configOverride?: IConfig) => {
|
|
|
158
178
|
activeRoomJID: string,
|
|
159
179
|
isReply: boolean = false,
|
|
160
180
|
isChecked: boolean = false,
|
|
161
|
-
mainMessage: string = ''
|
|
181
|
+
mainMessage: string = '',
|
|
182
|
+
existingId?: string
|
|
162
183
|
) => {
|
|
163
184
|
client?.onCriticalSend?.(activeRoomJID);
|
|
164
185
|
|
|
165
|
-
const id = `send-media-message:${uuidv4()}`;
|
|
186
|
+
const id = existingId || `send-media-message:${uuidv4()}`;
|
|
166
187
|
const optimisticTimestamp = Date.now();
|
|
167
188
|
const optimisticDate = new Date(optimisticTimestamp).toISOString();
|
|
168
189
|
const selfId =
|
|
@@ -171,7 +192,7 @@ export const useSendMessage = (_configOverride?: IConfig) => {
|
|
|
171
192
|
`${user?.firstName || ''} ${user?.lastName || ''}`.trim() || selfId;
|
|
172
193
|
const fileSizeStr = data?.size != null ? String(data.size) : '';
|
|
173
194
|
|
|
174
|
-
if (!config?.disableSentLogic) {
|
|
195
|
+
if (!config?.disableSentLogic && !existingId) {
|
|
175
196
|
dispatch(
|
|
176
197
|
addRoomMessage({
|
|
177
198
|
roomJID: activeRoomJID,
|
|
@@ -290,6 +311,18 @@ export const useSendMessage = (_configOverride?: IConfig) => {
|
|
|
290
311
|
requestUrl: error?.config?.url,
|
|
291
312
|
axiosMessage: error?.message,
|
|
292
313
|
});
|
|
314
|
+
dispatch(
|
|
315
|
+
markMessageFailed({
|
|
316
|
+
kind: 'media',
|
|
317
|
+
id,
|
|
318
|
+
roomJID: activeRoomJID,
|
|
319
|
+
data,
|
|
320
|
+
type,
|
|
321
|
+
isReply,
|
|
322
|
+
isChecked,
|
|
323
|
+
mainMessage,
|
|
324
|
+
})
|
|
325
|
+
);
|
|
293
326
|
handleMessageFailed({
|
|
294
327
|
message: 'media',
|
|
295
328
|
roomJID: activeRoomJID,
|
|
@@ -301,6 +334,43 @@ export const useSendMessage = (_configOverride?: IConfig) => {
|
|
|
301
334
|
[client, config, user, dispatch, handleMessageSent, handleMessageFailed]
|
|
302
335
|
);
|
|
303
336
|
|
|
337
|
+
// Retry a previously-failed send by replaying the saved payload from
|
|
338
|
+
// the heap. Clears the failure flag first so the bubble flips back to
|
|
339
|
+
// "sending..."; if the retry fails again `markMessageFailed` re-arms
|
|
340
|
+
// it. No-op when the id isn't in the failed map.
|
|
341
|
+
const retryMessage = useCallback(
|
|
342
|
+
async (failedId: string) => {
|
|
343
|
+
const payload = failedMessages[failedId];
|
|
344
|
+
if (!payload) {return;}
|
|
345
|
+
dispatch(clearMessageFailure(failedId));
|
|
346
|
+
handleMessageRetry({
|
|
347
|
+
messageId: failedId,
|
|
348
|
+
roomJID: payload.roomJID,
|
|
349
|
+
messageType: payload.kind,
|
|
350
|
+
});
|
|
351
|
+
if (payload.kind === 'text') {
|
|
352
|
+
await sendMessage(
|
|
353
|
+
payload.body,
|
|
354
|
+
payload.roomJID,
|
|
355
|
+
payload.isReply,
|
|
356
|
+
payload.isChecked,
|
|
357
|
+
payload.mainMessage
|
|
358
|
+
);
|
|
359
|
+
} else {
|
|
360
|
+
await sendMedia(
|
|
361
|
+
payload.data,
|
|
362
|
+
payload.type,
|
|
363
|
+
payload.roomJID,
|
|
364
|
+
payload.isReply,
|
|
365
|
+
payload.isChecked,
|
|
366
|
+
payload.mainMessage,
|
|
367
|
+
failedId
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
[failedMessages, dispatch, handleMessageRetry, sendMessage, sendMedia]
|
|
372
|
+
);
|
|
373
|
+
|
|
304
374
|
// ChatRoom/ThreadWrapper consume this as the "edit branch" of send.
|
|
305
375
|
// It mirrors the edit path inside `sendMessage` so callers can wire it
|
|
306
376
|
// up directly when `editAction.isEdit` is true.
|
|
@@ -355,6 +425,7 @@ export const useSendMessage = (_configOverride?: IConfig) => {
|
|
|
355
425
|
sendMessage,
|
|
356
426
|
sendMedia,
|
|
357
427
|
sendEditMessage,
|
|
428
|
+
retryMessage,
|
|
358
429
|
isLastMessageFromUserAndProcessing,
|
|
359
430
|
};
|
|
360
431
|
};
|
|
@@ -134,7 +134,11 @@ export function uploadFile(formData: FormData) {
|
|
|
134
134
|
headers: {
|
|
135
135
|
Authorization: token,
|
|
136
136
|
Accept: '*/*',
|
|
137
|
-
'Content-Type':
|
|
137
|
+
'Content-Type': null,
|
|
138
|
+
},
|
|
139
|
+
transformRequest: (data: any, headers: any) => {
|
|
140
|
+
if (headers?.delete) {headers.delete('Content-Type');}
|
|
141
|
+
return data;
|
|
138
142
|
},
|
|
139
143
|
});
|
|
140
144
|
}
|