fix(ui): reliable attachment download and preview error IPC

Export bytes to download-cache, use saved-path for large files, and always
send download/preview error events when fetch fails.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Raven Scott
2026-05-25 00:17:50 -04:00
co-authored by Cursor
parent a32e4a497e
commit c54e06f390
+63 -29
View File
@@ -1384,47 +1384,81 @@ async function dispatchUiMessage (platform, msg, hooks = {}) {
return true
}
if (t === 'request-attachment-preview') {
const preview = await platform.readAttachmentPreview(msg.attachmentId)
send({
type: 'attachment-preview',
preview,
fetchSource: preview?.fetchSource || null
})
const attachmentId = msg.attachmentId
try {
const preview = await platform.readAttachmentPreview(attachmentId)
send({
type: 'attachment-preview',
preview,
fetchSource: preview?.fetchSource || null
})
} catch (err) {
try {
send({
type: 'attachment-preview-error',
attachmentId,
message: err?.message || String(err)
})
} catch {
/* pipe closed */
}
}
return true
}
if (t === 'request-attachment-download') {
const attachmentId = msg.attachmentId
const downloadTimeoutMs = Number(process.env.PEARCORD_ATTACHMENT_DOWNLOAD_MS || 30000)
const maxInlineBytes = Number(process.env.PEARCORD_ATTACHMENT_DOWNLOAD_MAX || 6 * 1024 * 1024)
const maxInlineBytes = Number(process.env.PEARCORD_ATTACHMENT_DOWNLOAD_MAX || 512 * 1024)
const sendDownloadError = (message) => {
try {
send({
type: 'attachment-download-error',
attachmentId,
message: message || 'download failed'
})
} catch {
/* pipe closed */
}
}
try {
const row = await platform.attachments?.get(attachmentId)
if (!row) throw new Error('attachment not found')
const { buf, source } = await Promise.race([
platform.readAttachmentBytesWithSource(attachmentId),
const exported = await Promise.race([
platform.exportAttachmentForDownload(attachmentId),
new Promise((_, reject) => {
setTimeout(() => reject(new Error('download timed out')), downloadTimeoutMs)
})
])
if (!buf?.length) throw new Error('attachment file empty')
if (buf.length > maxInlineBytes) {
throw new Error(
`file too large to download inline (${Math.round(buf.length / 1024 / 1024)} MB)`
)
if (!exported?.filePath) throw new Error('attachment file empty')
if (exported.size > maxInlineBytes) {
try {
send({
type: 'attachment-download-saved',
attachmentId,
filename: exported.filename || 'download',
filePath: exported.filePath,
mimeType: exported.mimeType || 'application/octet-stream',
size: exported.size
})
} catch (err) {
sendDownloadError(err?.message || String(err))
}
return true
}
const fs = require('bare-fs')
const buf = fs.readFileSync(exported.filePath)
try {
send({
type: 'attachment-download',
attachmentId,
filename: exported.filename || 'download',
mimeType: exported.mimeType || 'application/octet-stream',
dataBase64: b4a.toString(buf, 'base64'),
fetchSource: 'export-cache'
})
} catch (err) {
sendDownloadError(err?.message || String(err))
}
send({
type: 'attachment-download',
attachmentId: row.id,
filename: row.filename || 'download',
mimeType: row.mimeType || 'application/octet-stream',
dataBase64: b4a.toString(buf, 'base64'),
fetchSource: source
})
} catch (err) {
send({
type: 'attachment-download-error',
attachmentId,
message: err?.message || String(err)
})
sendDownloadError(err?.message || String(err))
}
return true
}