feat(web-media): improve upload/send reliability
Some checks failed
CI / test (push) Failing after 17s
Some checks failed
CI / test (push) Failing after 17s
- add retry logic for presigned media upload - add retry for text send with idempotent client message id - avoid dropping already sent media message when attachment metadata call fails
This commit is contained in:
@@ -104,17 +104,32 @@ export async function uploadToPresignedUrl(
|
||||
uploadUrl: string,
|
||||
requiredHeaders: Record<string, string>,
|
||||
file: File,
|
||||
onProgress?: (percent: number) => void
|
||||
onProgress?: (percent: number) => void,
|
||||
retries = 2
|
||||
): Promise<void> {
|
||||
await axios.put(uploadUrl, file, {
|
||||
headers: requiredHeaders,
|
||||
onUploadProgress: (progressEvent) => {
|
||||
if (!onProgress || !progressEvent.total) {
|
||||
return;
|
||||
let attempt = 0;
|
||||
while (attempt <= retries) {
|
||||
try {
|
||||
await axios.put(uploadUrl, file, {
|
||||
headers: requiredHeaders,
|
||||
onUploadProgress: (progressEvent) => {
|
||||
if (!onProgress || !progressEvent.total) {
|
||||
return;
|
||||
}
|
||||
onProgress(Math.round((progressEvent.loaded * 100) / progressEvent.total));
|
||||
}
|
||||
});
|
||||
return;
|
||||
} catch (error) {
|
||||
const status = axios.isAxiosError(error) ? error.response?.status : undefined;
|
||||
const retryable = status === undefined || status >= 500;
|
||||
if (!retryable || attempt >= retries) {
|
||||
throw error;
|
||||
}
|
||||
onProgress(Math.round((progressEvent.loaded * 100) / progressEvent.total));
|
||||
attempt += 1;
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 400 * attempt));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function attachFile(messageId: number, fileUrl: string, fileType: string, fileSize: number): Promise<void> {
|
||||
|
||||
@@ -77,7 +77,21 @@ export function MessageComposer() {
|
||||
const replyToMessageId = activeChatId ? (replyToByChat[activeChatId]?.id ?? undefined) : undefined;
|
||||
addOptimisticMessage({ chatId: activeChatId, senderId: me.id, type: "text", text: textValue, clientMessageId });
|
||||
try {
|
||||
const message = await sendMessageWithClientId(activeChatId, textValue, "text", clientMessageId, replyToMessageId);
|
||||
let message = null;
|
||||
for (let attempt = 0; attempt < 2; attempt += 1) {
|
||||
try {
|
||||
message = await sendMessageWithClientId(activeChatId, textValue, "text", clientMessageId, replyToMessageId);
|
||||
break;
|
||||
} catch (error) {
|
||||
if (attempt === 1) {
|
||||
throw error;
|
||||
}
|
||||
await new Promise((resolve) => window.setTimeout(resolve, 250));
|
||||
}
|
||||
}
|
||||
if (!message) {
|
||||
throw new Error("send failed");
|
||||
}
|
||||
confirmMessageByClientId(activeChatId, clientMessageId, message);
|
||||
setText("");
|
||||
clearDraft(activeChatId);
|
||||
@@ -110,8 +124,12 @@ export function MessageComposer() {
|
||||
clientMessageId
|
||||
});
|
||||
const message = await sendMessageWithClientId(activeChatId, upload.file_url, messageType, clientMessageId, replyToMessageId);
|
||||
await attachFile(message.id, upload.file_url, file.type || "application/octet-stream", file.size);
|
||||
confirmMessageByClientId(activeChatId, clientMessageId, message);
|
||||
try {
|
||||
await attachFile(message.id, upload.file_url, file.type || "application/octet-stream", file.size);
|
||||
} catch {
|
||||
setUploadError("File sent, but metadata save failed. Please refresh chat.");
|
||||
}
|
||||
setReplyToMessage(activeChatId, null);
|
||||
} catch {
|
||||
removeOptimisticMessage(activeChatId, clientMessageId);
|
||||
|
||||
Reference in New Issue
Block a user