fix: Bug - Sending files between users doesn't really work

Stream oversized generic attachments to disk instead of silently dropping chunks, avoid loading completed file downloads into renderer memory, and surface a clear error when the browser client cannot receive a file.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-13 21:50:21 +02:00
co-authored by Cursor
parent 924d4bbb1d
commit 95259e8943
7 changed files with 187 additions and 9 deletions
@@ -9,13 +9,20 @@ import { AttachmentStorageService } from '../../infrastructure/services/attachme
import { MAX_AUTO_SAVE_SIZE_BYTES } from '../../domain/constants/attachment.constants';
import { isImageAttachment, resolvePublishAttachmentIsImage } from '../../domain/logic/attachment-image.rules';
import { isSharingFromThisDevice } from '../../domain/logic/attachment-sharing.rules';
import { shouldCopyUploaderMediaToAppData, shouldPersistDownloadedAttachment } from '../../domain/logic/attachment.logic';
import {
canReceiveAttachment,
isAttachmentMedia,
shouldCopyUploaderMediaToAppData,
shouldPersistDownloadedAttachment,
shouldStreamAttachmentReceiveToDisk
} from '../../domain/logic/attachment.logic';
import type { Attachment, AttachmentMeta } from '../../domain/models/attachment.model';
import {
ATTACHMENT_TRANSFER_EWMA_CURRENT_WEIGHT,
ATTACHMENT_TRANSFER_EWMA_PREVIOUS_WEIGHT,
DEFAULT_ATTACHMENT_MIME_TYPE,
ATTACHMENT_DOWNLOAD_FAILED_KEY,
ATTACHMENT_FILE_TOO_LARGE_KEY,
ATTACHMENT_CHUNKS_OUT_OF_ORDER_KEY,
ATTACHMENT_OPEN_DOWNLOAD_FAILED_KEY,
ATTACHMENT_PREPARE_DOWNLOAD_FAILED_KEY,
@@ -188,6 +195,13 @@ export class AttachmentTransferService {
return;
}
if (!canReceiveAttachment(attachment, this.receiveCapabilities())) {
this.runtimeStore.deletePendingRequest(requestKey);
attachment.requestError = this.appI18n.instant(ATTACHMENT_FILE_TOO_LARGE_KEY);
this.runtimeStore.touch();
return;
}
if (clearedRequestError)
this.runtimeStore.touch();
@@ -344,7 +358,9 @@ export class AttachmentTransferService {
return;
}
if (!this.shouldReceiveToDisk(attachment) && attachment.size > MAX_AUTO_SAVE_SIZE_BYTES) {
if (!canReceiveAttachment(attachment, this.receiveCapabilities())) {
attachment.requestError = this.appI18n.instant(ATTACHMENT_FILE_TOO_LARGE_KEY);
this.runtimeStore.touch();
return;
}
@@ -784,10 +800,14 @@ export class AttachmentTransferService {
}
private shouldReceiveToDisk(attachment: Attachment): boolean {
return this.isPlayableMedia(attachment) &&
!attachment.filePath &&
this.attachmentStorage.canStreamToDisk() &&
this.attachmentStorage.canPersistSize(attachment.size);
return shouldStreamAttachmentReceiveToDisk(attachment, this.receiveCapabilities());
}
private receiveCapabilities() {
return {
canStreamToDisk: this.attachmentStorage.canStreamToDisk(),
canPersistSize: (bytes: number) => this.attachmentStorage.canPersistSize(bytes)
};
}
private enqueueDiskFileChunk(
@@ -851,6 +871,14 @@ export class AttachmentTransferService {
attachment.savedPath = assembly.path;
if (!isAttachmentMedia(attachment)) {
attachment.available = true;
this.diskReceiveAssemblies.delete(assemblyKey);
this.runtimeStore.touch();
void this.persistence.persistAttachmentMeta(attachment);
return;
}
const restoredForDisplay = await this.persistence.ensureInlineDisplayObjectUrl(attachment);
if (!restoredForDisplay) {