feat(web-media): improve upload/send reliability
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:
2026-03-08 02:20:24 +03:00
parent 159a8ba516
commit 34edf2bae5
2 changed files with 43 additions and 10 deletions

View File

@@ -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> {

View File

@@ -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);