@droppii-org/chat-sdk 0.7.6 → 0.7.8
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/dist/components/conversation/ThreadCard.d.ts +2 -1
- package/dist/components/conversation/ThreadCard.d.ts.map +1 -1
- package/dist/components/conversation/ThreadCard.js +2 -2
- package/dist/components/conversation-detail/ConversationDetailHeader.d.ts +5 -1
- package/dist/components/conversation-detail/ConversationDetailHeader.d.ts.map +1 -1
- package/dist/components/conversation-detail/ConversationDetailHeader.js +3 -3
- package/dist/components/conversation-detail/ConversationMoreOptionPanel.d.ts.map +1 -1
- package/dist/components/conversation-detail/ConversationMoreOptionPanel.js +3 -1
- package/dist/components/conversation-detail/ThreadProfilePanel.d.ts +5 -1
- package/dist/components/conversation-detail/ThreadProfilePanel.d.ts.map +1 -1
- package/dist/components/conversation-detail/ThreadProfilePanel.js +13 -3
- package/dist/components/conversation-detail/profile/PinThreadLimitModal.d.ts +7 -0
- package/dist/components/conversation-detail/profile/PinThreadLimitModal.d.ts.map +1 -0
- package/dist/components/conversation-detail/profile/PinThreadLimitModal.js +14 -0
- package/dist/components/conversation-detail/profile/ThreadProfileActionRows.d.ts +15 -0
- package/dist/components/conversation-detail/profile/ThreadProfileActionRows.d.ts.map +1 -0
- package/dist/components/conversation-detail/profile/ThreadProfileActionRows.js +20 -0
- package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.d.ts +11 -0
- package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.d.ts.map +1 -0
- package/dist/components/conversation-detail/profile/ThreadProfileQuickActions.js +36 -0
- package/dist/components/conversation-detail/thread-search/ThreadSearchContent.d.ts +13 -0
- package/dist/components/conversation-detail/thread-search/ThreadSearchContent.d.ts.map +1 -0
- package/dist/components/conversation-detail/thread-search/ThreadSearchContent.js +70 -0
- package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.d.ts +11 -0
- package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.d.ts.map +1 -0
- package/dist/components/conversation-detail/thread-search/ThreadSearchPanel.js +12 -0
- package/dist/components/conversation-detail/thread-search/ThreadSearchResultItem.d.ts +11 -0
- package/dist/components/conversation-detail/thread-search/ThreadSearchResultItem.d.ts.map +1 -0
- package/dist/components/conversation-detail/thread-search/ThreadSearchResultItem.js +24 -0
- package/dist/components/conversation-detail/thread-search/utils.d.ts +8 -0
- package/dist/components/conversation-detail/thread-search/utils.d.ts.map +1 -0
- package/dist/components/conversation-detail/thread-search/utils.js +17 -0
- package/dist/components/conversation-inbox/ConversationInboxItem.d.ts +3 -1
- package/dist/components/conversation-inbox/ConversationInboxItem.d.ts.map +1 -1
- package/dist/components/conversation-inbox/ConversationInboxItem.js +4 -6
- package/dist/components/conversation-inbox/ConversationInboxList.d.ts +3 -1
- package/dist/components/conversation-inbox/ConversationInboxList.d.ts.map +1 -1
- package/dist/components/conversation-inbox/ConversationInboxList.js +2 -2
- package/dist/components/conversation-inbox/ConversationThreadActions.d.ts +12 -0
- package/dist/components/conversation-inbox/ConversationThreadActions.d.ts.map +1 -0
- package/dist/components/conversation-inbox/ConversationThreadActions.js +58 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchCommunitySection.d.ts +10 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchCommunitySection.d.ts.map +1 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchCommunitySection.js +15 -0
- package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.d.ts.map +1 -1
- package/dist/components/conversation-inbox/contact-search/ContactSearchPanel.js +39 -28
- package/dist/components/conversation-inbox/contact-search/constants.js +2 -2
- package/dist/components/conversation-inbox/index.d.ts +2 -2
- package/dist/components/conversation-inbox/index.d.ts.map +1 -1
- package/dist/components/conversation-inbox/index.js +1 -1
- package/dist/components/group/MemberPermissionPanel.d.ts.map +1 -1
- package/dist/components/group/MemberPermissionPanel.js +12 -23
- package/dist/components/message/toolbox/MessageToolboxMenu.d.ts +7 -0
- package/dist/components/message/toolbox/MessageToolboxMenu.d.ts.map +1 -1
- package/dist/components/message/toolbox/MessageToolboxMenu.js +1 -1
- package/dist/constants/moreOption.d.ts +1 -0
- package/dist/constants/moreOption.d.ts.map +1 -1
- package/dist/hooks/conversation/useBotFlow.d.ts.map +1 -1
- package/dist/hooks/conversation/useBotFlow.js +42 -23
- package/dist/hooks/conversation/useConversationTarget.d.ts +4 -0
- package/dist/hooks/conversation/useConversationTarget.d.ts.map +1 -0
- package/dist/hooks/conversation/useConversationTarget.js +23 -0
- package/dist/hooks/conversation/usePinConversation.d.ts +10 -0
- package/dist/hooks/conversation/usePinConversation.d.ts.map +1 -0
- package/dist/hooks/conversation/usePinConversation.js +52 -0
- package/dist/hooks/message/holdMessageHighlight.d.ts +6 -0
- package/dist/hooks/message/holdMessageHighlight.d.ts.map +1 -0
- package/dist/hooks/message/holdMessageHighlight.js +22 -0
- package/dist/hooks/message/useMessageListScroll.d.ts.map +1 -1
- package/dist/hooks/message/useMessageListScroll.js +13 -0
- package/dist/hooks/message/useSendMessage.d.ts +1 -1
- package/dist/hooks/message/useSendMessage.d.ts.map +1 -1
- package/dist/hooks/message/useSendMessage.js +358 -285
- package/dist/hooks/search/useSearchPublicGroups.d.ts +1 -0
- package/dist/hooks/search/useSearchPublicGroups.d.ts.map +1 -1
- package/dist/hooks/search/useSearchPublicGroups.js +6 -0
- package/dist/hooks/search/useSearchThreadMessages.d.ts +222 -0
- package/dist/hooks/search/useSearchThreadMessages.d.ts.map +1 -0
- package/dist/hooks/search/useSearchThreadMessages.js +60 -0
- package/dist/locales/vi/conversation-inbox.json +31 -0
- package/dist/screens/conversation-detail/index.d.ts +5 -1
- package/dist/screens/conversation-detail/index.d.ts.map +1 -1
- package/dist/screens/conversation-detail/index.js +17 -2
- package/dist/screens/conversation-inbox/index.d.ts +6 -0
- package/dist/screens/conversation-inbox/index.d.ts.map +1 -1
- package/dist/screens/conversation-inbox/index.js +3 -3
- package/dist/services/query.d.ts +3 -0
- package/dist/services/query.d.ts.map +1 -1
- package/dist/services/query.js +3 -0
- package/dist/services/routes.d.ts +2 -0
- package/dist/services/routes.d.ts.map +1 -1
- package/dist/services/routes.js +2 -0
- package/dist/store/conversation.d.ts.map +1 -1
- package/dist/store/conversation.js +10 -6
- package/dist/store/type.d.ts +3 -0
- package/dist/store/type.d.ts.map +1 -1
- package/dist/styles/global.css +1 -1
- package/dist/types/conversation.d.ts +8 -0
- package/dist/types/conversation.d.ts.map +1 -1
- package/dist/utils/common.d.ts +7 -0
- package/dist/utils/common.d.ts.map +1 -1
- package/dist/utils/common.js +42 -13
- package/dist/utils/sendQueue.d.ts +2 -0
- package/dist/utils/sendQueue.d.ts.map +1 -0
- package/dist/utils/sendQueue.js +14 -0
- package/package.json +1 -1
- package/dist/components/conversation-inbox/ConversationMuteSwipeAction.d.ts +0 -8
- package/dist/components/conversation-inbox/ConversationMuteSwipeAction.d.ts.map +0 -1
- package/dist/components/conversation-inbox/ConversationMuteSwipeAction.js +0 -31
|
@@ -18,6 +18,7 @@ import { updateSession } from "../../hooks/session/useUpdateSession";
|
|
|
18
18
|
import { invalidateSessionQueries } from "../../utils/queryHelpers";
|
|
19
19
|
import { resolveConversationBotId } from "../../utils/conversation";
|
|
20
20
|
import { QUERY_KEYS } from "../../services/query";
|
|
21
|
+
import { enqueue } from "../../utils/sendQueue";
|
|
21
22
|
const messageFileStore = new Map();
|
|
22
23
|
const MEDIA_CONTENT_TYPES = new Set([
|
|
23
24
|
MessageType.PictureMessage,
|
|
@@ -149,6 +150,120 @@ export const createQuoteMessage = async (text, quotedMessage, urls) => {
|
|
|
149
150
|
});
|
|
150
151
|
return textMessage;
|
|
151
152
|
};
|
|
153
|
+
// Rebuilds a failed message under a new clientMsgID, re-attaching its stored files.
|
|
154
|
+
const recreateFailedMessage = async (failedMessage) => {
|
|
155
|
+
var _a, _b;
|
|
156
|
+
const storedFiles = messageFileStore.get(failedMessage.clientMsgID);
|
|
157
|
+
if (!storedFiles) {
|
|
158
|
+
const textContent = ((_a = failedMessage.textElem) === null || _a === void 0 ? void 0 : _a.content) || failedMessage.content || "";
|
|
159
|
+
if (failedMessage.contentType === MessageType.CustomMessage) {
|
|
160
|
+
return failedMessage.customElem
|
|
161
|
+
? createCustomMessageByParams({
|
|
162
|
+
data: failedMessage.customElem.data,
|
|
163
|
+
extension: failedMessage.customElem.extension,
|
|
164
|
+
description: failedMessage.customElem.description,
|
|
165
|
+
})
|
|
166
|
+
: null;
|
|
167
|
+
}
|
|
168
|
+
if (failedMessage.contentType === MessageType.UrlTextMessage &&
|
|
169
|
+
failedMessage.urlTextElem) {
|
|
170
|
+
return createUrlTextMessage(textContent, failedMessage.urlTextElem.urls);
|
|
171
|
+
}
|
|
172
|
+
if (failedMessage.contentType === MessageType.StickerMessage) {
|
|
173
|
+
return ((_b = failedMessage.stickerElem) === null || _b === void 0 ? void 0 : _b.content)
|
|
174
|
+
? createStickerMessage(failedMessage.stickerElem.content)
|
|
175
|
+
: null;
|
|
176
|
+
}
|
|
177
|
+
return createTextMessage(textContent);
|
|
178
|
+
}
|
|
179
|
+
let newPayload = null;
|
|
180
|
+
if (storedFiles.type === "image" && failedMessage.pictureElem) {
|
|
181
|
+
const { sourcePicture, bigPicture, snapshotPicture, sourcePath } = failedMessage.pictureElem;
|
|
182
|
+
newPayload = await createImageMessageByFile({
|
|
183
|
+
sourcePicture,
|
|
184
|
+
bigPicture,
|
|
185
|
+
snapshotPicture,
|
|
186
|
+
sourcePath,
|
|
187
|
+
file: storedFiles.file,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
else if (storedFiles.type === "video" && failedMessage.videoElem) {
|
|
191
|
+
const elem = failedMessage.videoElem;
|
|
192
|
+
newPayload = await createVideoMessageByFile({
|
|
193
|
+
videoPath: elem.videoPath,
|
|
194
|
+
duration: elem.duration,
|
|
195
|
+
videoType: elem.videoType,
|
|
196
|
+
snapshotPath: elem.snapshotPath,
|
|
197
|
+
videoUUID: elem.videoUUID,
|
|
198
|
+
videoUrl: elem.videoUrl,
|
|
199
|
+
videoSize: elem.videoSize,
|
|
200
|
+
snapshotUUID: elem.snapshotUUID,
|
|
201
|
+
snapshotSize: elem.snapshotSize,
|
|
202
|
+
snapshotUrl: elem.snapshotUrl,
|
|
203
|
+
snapshotWidth: elem.snapshotWidth,
|
|
204
|
+
snapshotHeight: elem.snapshotHeight,
|
|
205
|
+
videoFile: storedFiles.videoFile,
|
|
206
|
+
snapshotFile: storedFiles.snapshotFile,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
else if (storedFiles.type === "file" && failedMessage.fileElem) {
|
|
210
|
+
const elem = failedMessage.fileElem;
|
|
211
|
+
newPayload = await createFileMessageByFile({
|
|
212
|
+
filePath: elem.filePath,
|
|
213
|
+
fileName: elem.fileName,
|
|
214
|
+
uuid: elem.uuid,
|
|
215
|
+
sourceUrl: elem.sourceUrl,
|
|
216
|
+
fileSize: elem.fileSize,
|
|
217
|
+
file: storedFiles.file,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
if (newPayload) {
|
|
221
|
+
messageFileStore.delete(failedMessage.clientMsgID);
|
|
222
|
+
messageFileStore.set(newPayload.clientMsgID, storedFiles);
|
|
223
|
+
}
|
|
224
|
+
return newPayload;
|
|
225
|
+
};
|
|
226
|
+
const SEND_HOLD_BASE_MS = 60000;
|
|
227
|
+
// About a 2 Mbps upload.
|
|
228
|
+
const SEND_HOLD_MIN_BYTES_PER_MS = 250;
|
|
229
|
+
const storedFileBytes = (clientMsgID) => {
|
|
230
|
+
const stored = messageFileStore.get(clientMsgID);
|
|
231
|
+
if (!stored)
|
|
232
|
+
return 0;
|
|
233
|
+
return stored.type === "video"
|
|
234
|
+
? stored.videoFile.size + stored.snapshotFile.size
|
|
235
|
+
: stored.file.size;
|
|
236
|
+
};
|
|
237
|
+
// How long a send may hold back the next queued message. Past this the next one goes
|
|
238
|
+
// anyway, so a stalled upload cannot block the thread for good.
|
|
239
|
+
const sendHoldLimitMs = (message) => {
|
|
240
|
+
var _a, _b;
|
|
241
|
+
const items = (_b = (_a = message.mergeElem) === null || _a === void 0 ? void 0 : _a.multiMessage) !== null && _b !== void 0 ? _b : [message];
|
|
242
|
+
const bytes = items.reduce((sum, item) => sum + storedFileBytes(item.clientMsgID), 0);
|
|
243
|
+
return SEND_HOLD_BASE_MS + bytes / SEND_HOLD_MIN_BYTES_PER_MS;
|
|
244
|
+
};
|
|
245
|
+
const settleOrTimeout = (promise, ms) => new Promise((resolve) => {
|
|
246
|
+
const timer = setTimeout(resolve, ms);
|
|
247
|
+
void promise.finally(() => {
|
|
248
|
+
clearTimeout(timer);
|
|
249
|
+
resolve();
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
let unsentCount = 0;
|
|
253
|
+
const warnUnsent = (event) => {
|
|
254
|
+
event.preventDefault();
|
|
255
|
+
event.returnValue = "";
|
|
256
|
+
};
|
|
257
|
+
// Queued messages live only in memory until the SDK takes them, so a reload would drop them.
|
|
258
|
+
const trackUnsent = (sending) => {
|
|
259
|
+
if (unsentCount++ === 0)
|
|
260
|
+
window.addEventListener("beforeunload", warnUnsent);
|
|
261
|
+
return sending.finally(() => {
|
|
262
|
+
if (--unsentCount === 0) {
|
|
263
|
+
window.removeEventListener("beforeunload", warnUnsent);
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
};
|
|
152
267
|
export const useSendMessage = (options) => {
|
|
153
268
|
var _a;
|
|
154
269
|
const { t } = useTranslation("common");
|
|
@@ -185,7 +300,7 @@ export const useSendMessage = (options) => {
|
|
|
185
300
|
console.error("syncSessionAfterPublicMessage", error);
|
|
186
301
|
}
|
|
187
302
|
}, [queryClient]);
|
|
188
|
-
const dispatchMessage = useCallback(async (message
|
|
303
|
+
const dispatchMessage = useCallback(async (message) => {
|
|
189
304
|
var _a, _b, _c, _d, _e, _f;
|
|
190
305
|
const plainText = ((_a = message === null || message === void 0 ? void 0 : message.textElem) === null || _a === void 0 ? void 0 : _a.content) ||
|
|
191
306
|
((_b = message === null || message === void 0 ? void 0 : message.quoteElem) === null || _b === void 0 ? void 0 : _b.text) ||
|
|
@@ -225,14 +340,10 @@ export const useSendMessage = (options) => {
|
|
|
225
340
|
},
|
|
226
341
|
});
|
|
227
342
|
updateOneMessage(Object.assign(Object.assign({}, successMessage), { ex: successMessage.ex || message.ex || "{}", isInternal: (_f = successMessage.isInternal) !== null && _f !== void 0 ? _f : message.isInternal }));
|
|
228
|
-
if (!message.isInternal) {
|
|
229
|
-
await syncSessionAfterPublicMessage(currentSession);
|
|
230
|
-
}
|
|
231
343
|
messageFileStore.delete(message.clientMsgID);
|
|
232
344
|
}, [
|
|
233
345
|
groupID,
|
|
234
346
|
recvID,
|
|
235
|
-
syncSessionAfterPublicMessage,
|
|
236
347
|
t,
|
|
237
348
|
conversationData === null || conversationData === void 0 ? void 0 : conversationData.showName,
|
|
238
349
|
conversationData === null || conversationData === void 0 ? void 0 : conversationData.faceURL,
|
|
@@ -240,334 +351,290 @@ export const useSendMessage = (options) => {
|
|
|
240
351
|
conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationType,
|
|
241
352
|
userId,
|
|
242
353
|
]);
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
const textContent = ((_a = failedMessage.textElem) === null || _a === void 0 ? void 0 : _a.content) || failedMessage.content || "";
|
|
262
|
-
if (failedMessage.contentType === MessageType.CustomMessage) {
|
|
263
|
-
newTextPayload = failedMessage.customElem
|
|
264
|
-
? await createCustomMessageByParams({
|
|
265
|
-
data: failedMessage.customElem.data,
|
|
266
|
-
extension: failedMessage.customElem.extension,
|
|
267
|
-
description: failedMessage.customElem.description,
|
|
268
|
-
})
|
|
269
|
-
: null;
|
|
270
|
-
}
|
|
271
|
-
else if (failedMessage.contentType === MessageType.UrlTextMessage &&
|
|
272
|
-
failedMessage.urlTextElem) {
|
|
273
|
-
newTextPayload = await createUrlTextMessage(textContent, failedMessage.urlTextElem.urls);
|
|
274
|
-
}
|
|
275
|
-
else if (failedMessage.contentType === MessageType.StickerMessage) {
|
|
276
|
-
newTextPayload = ((_b = failedMessage.stickerElem) === null || _b === void 0 ? void 0 : _b.content)
|
|
277
|
-
? await createStickerMessage(failedMessage.stickerElem.content)
|
|
278
|
-
: null;
|
|
279
|
-
}
|
|
280
|
-
else {
|
|
281
|
-
newTextPayload = await createTextMessage(textContent);
|
|
282
|
-
}
|
|
283
|
-
if (!newTextPayload) {
|
|
284
|
-
updateOneMessage(Object.assign(Object.assign({}, failedMessage), { status: MessageStatus.Failed }));
|
|
285
|
-
return;
|
|
286
|
-
}
|
|
287
|
-
const conversationID = (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) || "";
|
|
288
|
-
await DChatSDK.deleteMessageFromLocalStorage({
|
|
289
|
-
conversationID,
|
|
290
|
-
clientMsgID: failedMessage.clientMsgID,
|
|
291
|
-
});
|
|
292
|
-
removeOneMessage(failedMessage.clientMsgID);
|
|
293
|
-
await sendMessage(Object.assign(Object.assign({}, newTextPayload), { ex: failedMessage.ex, isInternal: failedMessage.isInternal }));
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
let newPayload = null;
|
|
297
|
-
if (storedFiles.type === "image" && failedMessage.pictureElem) {
|
|
298
|
-
const { sourcePicture, bigPicture, snapshotPicture, sourcePath } = failedMessage.pictureElem;
|
|
299
|
-
newPayload = await createImageMessageByFile({
|
|
300
|
-
sourcePicture,
|
|
301
|
-
bigPicture,
|
|
302
|
-
snapshotPicture,
|
|
303
|
-
sourcePath,
|
|
304
|
-
file: storedFiles.file,
|
|
305
|
-
});
|
|
306
|
-
}
|
|
307
|
-
else if (storedFiles.type === "video" && failedMessage.videoElem) {
|
|
308
|
-
const elem = failedMessage.videoElem;
|
|
309
|
-
newPayload = await createVideoMessageByFile({
|
|
310
|
-
videoPath: elem.videoPath,
|
|
311
|
-
duration: elem.duration,
|
|
312
|
-
videoType: elem.videoType,
|
|
313
|
-
snapshotPath: elem.snapshotPath,
|
|
314
|
-
videoUUID: elem.videoUUID,
|
|
315
|
-
videoUrl: elem.videoUrl,
|
|
316
|
-
videoSize: elem.videoSize,
|
|
317
|
-
snapshotUUID: elem.snapshotUUID,
|
|
318
|
-
snapshotSize: elem.snapshotSize,
|
|
319
|
-
snapshotUrl: elem.snapshotUrl,
|
|
320
|
-
snapshotWidth: elem.snapshotWidth,
|
|
321
|
-
snapshotHeight: elem.snapshotHeight,
|
|
322
|
-
videoFile: storedFiles.videoFile,
|
|
323
|
-
snapshotFile: storedFiles.snapshotFile,
|
|
324
|
-
});
|
|
325
|
-
}
|
|
326
|
-
else if (storedFiles.type === "file" && failedMessage.fileElem) {
|
|
327
|
-
const elem = failedMessage.fileElem;
|
|
328
|
-
newPayload = await createFileMessageByFile({
|
|
329
|
-
filePath: elem.filePath,
|
|
330
|
-
fileName: elem.fileName,
|
|
331
|
-
uuid: elem.uuid,
|
|
332
|
-
sourceUrl: elem.sourceUrl,
|
|
333
|
-
fileSize: elem.fileSize,
|
|
334
|
-
file: storedFiles.file,
|
|
354
|
+
const sendQueueKey = (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) || "";
|
|
355
|
+
// Bubbles show in call order and reach the server one at a time in that same order,
|
|
356
|
+
// so a text sent right after a large video cannot overtake the video's upload.
|
|
357
|
+
const queueSend = useCallback((build, currentSession) => {
|
|
358
|
+
const shown = enqueue(`show:${sendQueueKey}`, async () => {
|
|
359
|
+
const messages = await build();
|
|
360
|
+
if (!(messages === null || messages === void 0 ? void 0 : messages.length))
|
|
361
|
+
return null;
|
|
362
|
+
messages.forEach((message) => pushNewMessage(message));
|
|
363
|
+
emit(scrollEvent);
|
|
364
|
+
return messages;
|
|
365
|
+
});
|
|
366
|
+
const outcomes = [];
|
|
367
|
+
let isSessionSynced = false;
|
|
368
|
+
const queued = enqueue(`send:${sendQueueKey}`, async () => {
|
|
369
|
+
const messages = await shown.catch((error) => {
|
|
370
|
+
console.error("sendMessage", error);
|
|
371
|
+
return null;
|
|
335
372
|
});
|
|
336
|
-
|
|
373
|
+
for (const message of messages !== null && messages !== void 0 ? messages : []) {
|
|
374
|
+
const holdLimitMs = sendHoldLimitMs(message);
|
|
375
|
+
const outcome = dispatchMessage(message).then(() => {
|
|
376
|
+
if (!message.isInternal && !isSessionSynced) {
|
|
377
|
+
isSessionSynced = true;
|
|
378
|
+
void syncSessionAfterPublicMessage(currentSession);
|
|
379
|
+
}
|
|
380
|
+
return true;
|
|
381
|
+
}, (error) => {
|
|
382
|
+
console.error("sendMessage", error);
|
|
383
|
+
updateOneMessage(Object.assign(Object.assign({}, message), { status: MessageStatus.Failed }));
|
|
384
|
+
return false;
|
|
385
|
+
});
|
|
386
|
+
outcomes.push(outcome);
|
|
387
|
+
await settleOrTimeout(outcome, holdLimitMs);
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
return trackUnsent(queued
|
|
391
|
+
.then(() => Promise.all(outcomes))
|
|
392
|
+
.then((results) => results.length > 0 && results.every(Boolean)));
|
|
393
|
+
}, [sendQueueKey, dispatchMessage, scrollEvent, syncSessionAfterPublicMessage]);
|
|
394
|
+
const resendMessage = useCallback((failedMessage) => queueSend(async () => {
|
|
395
|
+
const newPayload = await recreateFailedMessage(failedMessage);
|
|
337
396
|
if (!newPayload) {
|
|
338
397
|
updateOneMessage(Object.assign(Object.assign({}, failedMessage), { status: MessageStatus.Failed }));
|
|
339
|
-
return;
|
|
398
|
+
return null;
|
|
340
399
|
}
|
|
341
|
-
messageFileStore.delete(failedMessage.clientMsgID);
|
|
342
|
-
messageFileStore.set(newPayload.clientMsgID, storedFiles);
|
|
343
|
-
const conversationID = (conversationData === null || conversationData === void 0 ? void 0 : conversationData.conversationID) || "";
|
|
344
400
|
await DChatSDK.deleteMessageFromLocalStorage({
|
|
345
|
-
conversationID,
|
|
401
|
+
conversationID: sendQueueKey,
|
|
346
402
|
clientMsgID: failedMessage.clientMsgID,
|
|
347
403
|
});
|
|
348
404
|
removeOneMessage(failedMessage.clientMsgID);
|
|
349
|
-
|
|
350
|
-
|
|
405
|
+
return [
|
|
406
|
+
Object.assign(Object.assign({}, newPayload), { ex: failedMessage.ex, isInternal: failedMessage.isInternal }),
|
|
407
|
+
];
|
|
408
|
+
}), [queueSend, sendQueueKey]);
|
|
351
409
|
const sendCustomMessage = useCallback(async (payload, sendOptions) => {
|
|
352
410
|
if (!conversationData || (!recvID && !groupID))
|
|
353
411
|
return false;
|
|
354
|
-
const customMessage = await createCustomMessage(payload);
|
|
355
|
-
if (!customMessage)
|
|
356
|
-
return false;
|
|
357
412
|
const extendMessageInfo = generateExtendMessageInfo({
|
|
358
413
|
currentSession: sendOptions === null || sendOptions === void 0 ? void 0 : sendOptions.currentSession,
|
|
359
414
|
});
|
|
360
|
-
return
|
|
361
|
-
|
|
415
|
+
return queueSend(async () => {
|
|
416
|
+
const customMessage = await createCustomMessage(payload);
|
|
417
|
+
if (!customMessage)
|
|
418
|
+
return null;
|
|
419
|
+
return [
|
|
420
|
+
Object.assign(Object.assign({}, customMessage), { ex: JSON.stringify(extendMessageInfo) || "{}", isInternal: false }),
|
|
421
|
+
];
|
|
422
|
+
}, sendOptions === null || sendOptions === void 0 ? void 0 : sendOptions.currentSession);
|
|
423
|
+
}, [conversationData, recvID, groupID, queueSend]);
|
|
362
424
|
const sendStickerMessage = useCallback(async (sticker, sendOptions) => {
|
|
363
425
|
var _a;
|
|
364
426
|
if (!conversationData || (!recvID && !groupID))
|
|
365
427
|
return false;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
const stickerMessage = await createStickerMessage(sticker.file.fullUrl);
|
|
369
|
-
if (!stickerMessage)
|
|
428
|
+
const stickerUrl = (_a = sticker.file) === null || _a === void 0 ? void 0 : _a.fullUrl;
|
|
429
|
+
if (!stickerUrl)
|
|
370
430
|
return false;
|
|
371
431
|
// BE records "recent stickers" only from ex.stickerId on the saved message.
|
|
372
432
|
const extendMessageInfo = generateExtendMessageInfo({
|
|
373
433
|
currentSession: sendOptions === null || sendOptions === void 0 ? void 0 : sendOptions.currentSession,
|
|
374
434
|
stickerId: sticker.id,
|
|
375
435
|
});
|
|
376
|
-
const isSent = await
|
|
436
|
+
const isSent = await queueSend(async () => {
|
|
437
|
+
const stickerMessage = await createStickerMessage(stickerUrl);
|
|
438
|
+
if (!stickerMessage)
|
|
439
|
+
return null;
|
|
440
|
+
return [
|
|
441
|
+
Object.assign(Object.assign({}, stickerMessage), { ex: JSON.stringify(extendMessageInfo) || "{}", isInternal: false }),
|
|
442
|
+
];
|
|
443
|
+
}, sendOptions === null || sendOptions === void 0 ? void 0 : sendOptions.currentSession);
|
|
377
444
|
if (isSent) {
|
|
378
445
|
queryClient.invalidateQueries({
|
|
379
446
|
queryKey: [QUERY_KEYS.GET_STICKER_HISTORY],
|
|
380
447
|
});
|
|
381
448
|
}
|
|
382
449
|
return isSent;
|
|
383
|
-
}, [conversationData, recvID, groupID,
|
|
450
|
+
}, [conversationData, recvID, groupID, queueSend, queryClient]);
|
|
384
451
|
const sendMergeMessage = useCallback(async ({ richText, plainText, files = [], currentSession, isInternal, urlMetadata, flow, customMessage, mentions, }) => {
|
|
385
452
|
if (!conversationData || (!recvID && !groupID))
|
|
386
453
|
return false;
|
|
387
|
-
const
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
const baseInfo = {
|
|
402
|
-
uuid: uuidv4(),
|
|
403
|
-
type: file.type,
|
|
404
|
-
size: file.size,
|
|
405
|
-
width: picInfo.width,
|
|
406
|
-
height: picInfo.height,
|
|
407
|
-
url: URL.createObjectURL(file),
|
|
408
|
-
};
|
|
409
|
-
const imageMessage = await createImageMessageByFile({
|
|
410
|
-
sourcePicture: baseInfo,
|
|
411
|
-
bigPicture: baseInfo,
|
|
412
|
-
snapshotPicture: baseInfo,
|
|
413
|
-
sourcePath: "",
|
|
414
|
-
file,
|
|
415
|
-
});
|
|
416
|
-
if (!imageMessage)
|
|
417
|
-
continue;
|
|
418
|
-
imageMessages.push(imageMessage);
|
|
419
|
-
}
|
|
420
|
-
catch (err) {
|
|
421
|
-
console.error("Lỗi xử lý ảnh:", err);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
if (imageMessages.length === 1) {
|
|
425
|
-
messageFileStore.set(imageMessages[0].clientMsgID, {
|
|
426
|
-
type: "image",
|
|
427
|
-
file: imageFileWrappers[0].originFileObj,
|
|
454
|
+
const hasText = !!plainText && plainText.trim() !== "";
|
|
455
|
+
// Clear the reply bar now rather than when earlier queued sends finish building.
|
|
456
|
+
const quoted = (quotedMessage === null || quotedMessage === void 0 ? void 0 : quotedMessage.clientMsgID) ? quotedMessage : undefined;
|
|
457
|
+
if (hasText && quotedMessage)
|
|
458
|
+
setQuotedMessage(null);
|
|
459
|
+
// Every bubble (image or album → videos → files → text) is built first and shown
|
|
460
|
+
// together, the same order the mobile app uses.
|
|
461
|
+
const buildMessages = async () => {
|
|
462
|
+
const messageList = [];
|
|
463
|
+
const imageFileWrappers = files.filter((f) => { var _a; return (_a = f.originFileObj) === null || _a === void 0 ? void 0 : _a.type.startsWith("image/"); });
|
|
464
|
+
const videoFileWrappers = files.filter((f) => { var _a; return (_a = f.originFileObj) === null || _a === void 0 ? void 0 : _a.type.startsWith("video/"); });
|
|
465
|
+
const documentFileWrappers = files.filter((f) => {
|
|
466
|
+
var _a;
|
|
467
|
+
return (_a = f.originFileObj) === null || _a === void 0 ? void 0 : _a.type.startsWith("application/");
|
|
428
468
|
});
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
messageFileStore.set(imageMsg.clientMsgID, {
|
|
434
|
-
type: "image",
|
|
435
|
-
file: imageFileWrappers[index].originFileObj,
|
|
436
|
-
});
|
|
437
|
-
});
|
|
438
|
-
const mergeMsg = await createMergerMessage({
|
|
439
|
-
messageList: imageMessages,
|
|
440
|
-
title: "Album ảnh",
|
|
441
|
-
summaryList: imageMessages.map(() => "[Hình ảnh]"),
|
|
442
|
-
});
|
|
443
|
-
if (mergeMsg)
|
|
444
|
-
messageList.push(mergeMsg);
|
|
445
|
-
}
|
|
446
|
-
for (const fileWrapper of videoFileWrappers) {
|
|
447
|
-
const file = fileWrapper.originFileObj;
|
|
448
|
-
if (!file)
|
|
449
|
-
continue;
|
|
450
|
-
try {
|
|
451
|
-
const videoBaseInfo = await createVideoInfoWithThumbnail(file);
|
|
452
|
-
const thumbFile = new File([videoBaseInfo.thumbnail], file.name + "-thumb.jpg", { type: "image/jpeg" });
|
|
453
|
-
const videoMessage = await createVideoMessageByFile({
|
|
454
|
-
videoPath: "",
|
|
455
|
-
duration: videoBaseInfo.duration,
|
|
456
|
-
videoType: file.type,
|
|
457
|
-
snapshotPath: "",
|
|
458
|
-
videoUUID: uuidv4(),
|
|
459
|
-
videoUrl: URL.createObjectURL(file),
|
|
460
|
-
videoSize: file.size,
|
|
461
|
-
snapshotUUID: "",
|
|
462
|
-
snapshotSize: file.size,
|
|
463
|
-
snapshotUrl: URL.createObjectURL(thumbFile),
|
|
464
|
-
snapshotWidth: videoBaseInfo.width,
|
|
465
|
-
snapshotHeight: videoBaseInfo.height,
|
|
466
|
-
snapShotType: file.type,
|
|
467
|
-
videoFile: file,
|
|
468
|
-
snapshotFile: thumbFile,
|
|
469
|
-
});
|
|
470
|
-
if (!videoMessage)
|
|
469
|
+
const imageMessages = [];
|
|
470
|
+
for (const fileWrapper of imageFileWrappers) {
|
|
471
|
+
const file = fileWrapper.originFileObj;
|
|
472
|
+
if (!file)
|
|
471
473
|
continue;
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
474
|
+
try {
|
|
475
|
+
const picInfo = await createPicBaseInfoFromFile(file);
|
|
476
|
+
const baseInfo = {
|
|
477
|
+
uuid: uuidv4(),
|
|
478
|
+
type: file.type,
|
|
479
|
+
size: file.size,
|
|
480
|
+
width: picInfo.width,
|
|
481
|
+
height: picInfo.height,
|
|
482
|
+
url: URL.createObjectURL(file),
|
|
483
|
+
};
|
|
484
|
+
const imageMessage = await createImageMessageByFile({
|
|
485
|
+
sourcePicture: baseInfo,
|
|
486
|
+
bigPicture: baseInfo,
|
|
487
|
+
snapshotPicture: baseInfo,
|
|
488
|
+
sourcePath: "",
|
|
489
|
+
file,
|
|
490
|
+
});
|
|
491
|
+
if (!imageMessage)
|
|
492
|
+
continue;
|
|
493
|
+
messageFileStore.set(imageMessage.clientMsgID, {
|
|
494
|
+
type: "image",
|
|
495
|
+
file,
|
|
496
|
+
});
|
|
497
|
+
imageMessages.push(imageMessage);
|
|
498
|
+
}
|
|
499
|
+
catch (err) {
|
|
500
|
+
console.error("Lỗi xử lý ảnh:", err);
|
|
501
|
+
}
|
|
478
502
|
}
|
|
479
|
-
|
|
480
|
-
|
|
503
|
+
if (imageMessages.length === 1) {
|
|
504
|
+
messageList.push(imageMessages[0]);
|
|
481
505
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
try {
|
|
488
|
-
const fileParams = {
|
|
489
|
-
filePath: "",
|
|
490
|
-
fileName: file.name,
|
|
491
|
-
uuid: uuidv4(),
|
|
492
|
-
sourceUrl: URL.createObjectURL(file),
|
|
493
|
-
fileSize: file.size,
|
|
494
|
-
fileType: file.type,
|
|
495
|
-
file: file,
|
|
496
|
-
};
|
|
497
|
-
const fileMessage = await createFileMessageByFile(fileParams);
|
|
498
|
-
if (!fileMessage)
|
|
499
|
-
continue;
|
|
500
|
-
messageFileStore.set(fileMessage.clientMsgID, {
|
|
501
|
-
type: "file",
|
|
502
|
-
file: file,
|
|
506
|
+
else if (imageMessages.length > 1) {
|
|
507
|
+
const mergeMsg = await createMergerMessage({
|
|
508
|
+
messageList: imageMessages,
|
|
509
|
+
title: "Album ảnh",
|
|
510
|
+
summaryList: imageMessages.map(() => "[Hình ảnh]"),
|
|
503
511
|
});
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
catch (err) {
|
|
507
|
-
console.error("Lỗi xử lý tin nhắn:", err);
|
|
512
|
+
if (mergeMsg)
|
|
513
|
+
messageList.push(mergeMsg);
|
|
508
514
|
}
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
:
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
515
|
+
for (const fileWrapper of videoFileWrappers) {
|
|
516
|
+
const file = fileWrapper.originFileObj;
|
|
517
|
+
if (!file)
|
|
518
|
+
continue;
|
|
519
|
+
try {
|
|
520
|
+
const videoBaseInfo = await createVideoInfoWithThumbnail(file);
|
|
521
|
+
const thumbFile = new File([videoBaseInfo.thumbnail], file.name + "-thumb.jpg", { type: "image/jpeg" });
|
|
522
|
+
const videoMessage = await createVideoMessageByFile({
|
|
523
|
+
videoPath: "",
|
|
524
|
+
duration: videoBaseInfo.duration,
|
|
525
|
+
videoType: file.type,
|
|
526
|
+
snapshotPath: "",
|
|
527
|
+
videoUUID: uuidv4(),
|
|
528
|
+
videoUrl: URL.createObjectURL(file),
|
|
529
|
+
videoSize: file.size,
|
|
530
|
+
snapshotUUID: "",
|
|
531
|
+
snapshotSize: file.size,
|
|
532
|
+
snapshotUrl: URL.createObjectURL(thumbFile),
|
|
533
|
+
snapshotWidth: videoBaseInfo.width,
|
|
534
|
+
snapshotHeight: videoBaseInfo.height,
|
|
535
|
+
snapShotType: file.type,
|
|
536
|
+
videoFile: file,
|
|
537
|
+
snapshotFile: thumbFile,
|
|
538
|
+
});
|
|
539
|
+
if (!videoMessage)
|
|
540
|
+
continue;
|
|
541
|
+
messageFileStore.set(videoMessage.clientMsgID, {
|
|
542
|
+
type: "video",
|
|
543
|
+
videoFile: file,
|
|
544
|
+
snapshotFile: thumbFile,
|
|
545
|
+
});
|
|
546
|
+
messageList.push(videoMessage);
|
|
547
|
+
}
|
|
548
|
+
catch (err) {
|
|
549
|
+
console.error("Lỗi xử lý video:", err);
|
|
550
|
+
}
|
|
527
551
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
552
|
+
for (const fileWrapper of documentFileWrappers) {
|
|
553
|
+
const file = fileWrapper.originFileObj;
|
|
554
|
+
if (!file)
|
|
555
|
+
continue;
|
|
556
|
+
try {
|
|
557
|
+
const fileParams = {
|
|
558
|
+
filePath: "",
|
|
559
|
+
fileName: file.name,
|
|
560
|
+
uuid: uuidv4(),
|
|
561
|
+
sourceUrl: URL.createObjectURL(file),
|
|
562
|
+
fileSize: file.size,
|
|
563
|
+
fileType: file.type,
|
|
564
|
+
file: file,
|
|
565
|
+
};
|
|
566
|
+
const fileMessage = await createFileMessageByFile(fileParams);
|
|
567
|
+
if (!fileMessage)
|
|
568
|
+
continue;
|
|
569
|
+
messageFileStore.set(fileMessage.clientMsgID, {
|
|
570
|
+
type: "file",
|
|
571
|
+
file: file,
|
|
572
|
+
});
|
|
573
|
+
messageList.push(fileMessage);
|
|
574
|
+
}
|
|
575
|
+
catch (err) {
|
|
576
|
+
console.error("Lỗi xử lý tin nhắn:", err);
|
|
577
|
+
}
|
|
531
578
|
}
|
|
532
|
-
|
|
533
|
-
|
|
579
|
+
const hasMentions = !!mentions && (mentions.atUserIDList.length > 0 || mentions.isAtAll);
|
|
580
|
+
if (hasText) {
|
|
581
|
+
const urls = extractLinkUrls(plainText);
|
|
582
|
+
let textMessage = null;
|
|
583
|
+
if (hasMentions && mentions) {
|
|
584
|
+
// OpenIM SDK convention: "tag all" is signaled by the sentinel
|
|
585
|
+
// "AtAllTag" inside atUserIDList, not a separate flag/param.
|
|
586
|
+
textMessage = await createAtTextMessage({
|
|
587
|
+
text: plainText,
|
|
588
|
+
atUserIDList: mentions.isAtAll
|
|
589
|
+
? [AT_ALL_TAG, ...mentions.atUserIDList]
|
|
590
|
+
: mentions.atUserIDList,
|
|
591
|
+
atUsersInfo: mentions.atUsersInfo,
|
|
592
|
+
message: quoted,
|
|
593
|
+
urls: urls || undefined,
|
|
594
|
+
});
|
|
595
|
+
}
|
|
596
|
+
else if (quoted) {
|
|
597
|
+
textMessage = await createQuoteMessage(plainText, quoted, urls || undefined);
|
|
598
|
+
}
|
|
599
|
+
else if (urls.length > 0) {
|
|
600
|
+
textMessage = await createUrlTextMessage(plainText, urls);
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
textMessage = await createTextMessage(plainText);
|
|
604
|
+
}
|
|
605
|
+
if (!textMessage)
|
|
606
|
+
return null;
|
|
607
|
+
messageList.push(textMessage);
|
|
534
608
|
}
|
|
535
|
-
|
|
536
|
-
|
|
609
|
+
if (customMessage) {
|
|
610
|
+
const customMessageItem = await createCustomMessage(customMessage);
|
|
611
|
+
if (!customMessageItem)
|
|
612
|
+
return null;
|
|
613
|
+
messageList.push(customMessageItem);
|
|
537
614
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
(
|
|
553
|
-
const extendMessageInfo = generateExtendMessageInfo({
|
|
554
|
-
richText: isTextLikeMessage ? richText : "",
|
|
555
|
-
urlMetadata: urlMetadata || undefined,
|
|
556
|
-
currentSession,
|
|
557
|
-
flow: isTextLikeMessage ? flow : undefined,
|
|
558
|
-
mentionOffsets: (message === null || message === void 0 ? void 0 : message.contentType) === MessageType.AtTextMessage
|
|
559
|
-
? mentions === null || mentions === void 0 ? void 0 : mentions.mentionOffsets
|
|
560
|
-
: undefined,
|
|
615
|
+
return messageList.map((message) => {
|
|
616
|
+
const isTextLikeMessage = (message === null || message === void 0 ? void 0 : message.contentType) === MessageType.TextMessage ||
|
|
617
|
+
(message === null || message === void 0 ? void 0 : message.contentType) === MessageType.UrlTextMessage ||
|
|
618
|
+
(message === null || message === void 0 ? void 0 : message.contentType) === MessageType.QuoteMessage ||
|
|
619
|
+
(message === null || message === void 0 ? void 0 : message.contentType) === MessageType.AtTextMessage;
|
|
620
|
+
const extendMessageInfo = generateExtendMessageInfo({
|
|
621
|
+
richText: isTextLikeMessage ? richText : "",
|
|
622
|
+
urlMetadata: urlMetadata || undefined,
|
|
623
|
+
currentSession,
|
|
624
|
+
flow: isTextLikeMessage ? flow : undefined,
|
|
625
|
+
mentionOffsets: (message === null || message === void 0 ? void 0 : message.contentType) === MessageType.AtTextMessage
|
|
626
|
+
? mentions === null || mentions === void 0 ? void 0 : mentions.mentionOffsets
|
|
627
|
+
: undefined,
|
|
628
|
+
});
|
|
629
|
+
return Object.assign(Object.assign({}, message), { ex: JSON.stringify(extendMessageInfo) || "{}", isInternal });
|
|
561
630
|
});
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
return true;
|
|
631
|
+
};
|
|
632
|
+
return queueSend(buildMessages, currentSession);
|
|
566
633
|
}, [
|
|
567
634
|
conversationData,
|
|
568
635
|
recvID,
|
|
569
636
|
groupID,
|
|
570
|
-
|
|
637
|
+
queueSend,
|
|
571
638
|
quotedMessage,
|
|
572
639
|
setQuotedMessage,
|
|
573
640
|
]);
|
|
@@ -614,10 +681,15 @@ const createPicBaseInfoFromFile = (file) => new Promise((resolve, reject) => {
|
|
|
614
681
|
img.onload = function () {
|
|
615
682
|
resolve(img);
|
|
616
683
|
};
|
|
684
|
+
// Without this a broken image would hang its send and every send queued after it.
|
|
685
|
+
img.onerror = reject;
|
|
617
686
|
img.src = _URL.createObjectURL(file);
|
|
618
687
|
});
|
|
688
|
+
const VIDEO_INFO_TIMEOUT_MS = 15000;
|
|
619
689
|
function createVideoInfoWithThumbnail(file) {
|
|
620
690
|
return new Promise((resolve, reject) => {
|
|
691
|
+
// A video the browser never seeks would hang its send and every send queued after it.
|
|
692
|
+
const timeout = setTimeout(() => reject(new Error("Video thumbnail timed out")), VIDEO_INFO_TIMEOUT_MS);
|
|
621
693
|
try {
|
|
622
694
|
const video = document.createElement("video");
|
|
623
695
|
video.preload = "metadata";
|
|
@@ -640,6 +712,7 @@ function createVideoInfoWithThumbnail(file) {
|
|
|
640
712
|
canvas.toBlob((blob) => {
|
|
641
713
|
if (!blob)
|
|
642
714
|
return reject("Thumbnail capture failed");
|
|
715
|
+
clearTimeout(timeout);
|
|
643
716
|
resolve({
|
|
644
717
|
duration: Math.floor(video.duration * 1000),
|
|
645
718
|
width: video.videoWidth,
|