From 5f7d8b460c929e23c7ac530a6172062c178a5112 Mon Sep 17 00:00:00 2001 From: Raven Scott Date: Fri, 22 May 2026 00:36:15 -0400 Subject: [PATCH] feat(attachments): expose fetchSource on preview and mesh retry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add readAttachmentBytesWithSource so callers know whether bytes came from local staging, drive-local, protomux attach-mesh, or hyperdrive replication. readAttachmentPreview now returns fetchSource for UI and smokes. _fetchAttachMeshWithRetry mirrors banner prefetch (14s timeout + 600ms retry when attach sessions exist). Banner prefetch uses the same retry helper. Phase 60 / v0.8.25 — Pear attach preview mesh path. Co-authored-by: Cursor --- index.js | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index d5bf8b8..fa2ac47 100644 --- a/index.js +++ b/index.js @@ -621,7 +621,7 @@ class PearcordPlatform extends EventEmitter { const row = await this.attachments.get(attachmentId) if (row && this.attachments.hasLocalBytes(row)) return row if (!this.guild) return null - const buf = await this.guild.fetchAttachmentFromPeers(attachmentId).catch(() => null) + const buf = await this._fetchAttachMeshWithRetry(attachmentId) if (!buf) return null const meta = row || (await this.attachments.get(attachmentId)) if (!meta) return null @@ -2124,21 +2124,33 @@ class PearcordPlatform extends EventEmitter { return row } - async readAttachmentBytes (attachmentId) { + async _fetchAttachMeshWithRetry (attachmentId, timeoutMs = 14000) { + if (!this.guild) return null + let buf = await this.guild.fetchAttachmentFromPeers(attachmentId, timeoutMs).catch(() => null) + if (buf?.length) return buf + if (!(this.guild._attachChannels?.size > 0)) return null + await new Promise((r) => setTimeout(r, 600)) + return this.guild.fetchAttachmentFromPeers(attachmentId, timeoutMs).catch(() => null) + } + + async readAttachmentBytesWithSource (attachmentId) { const row = await this.attachments?.get(attachmentId) if (!row) throw new Error('attachment not found') if (this.attachments.hasLocalBytes(row)) { - return this.attachments.readBytes(row) + return { buf: await this.attachments.readBytes(row), source: 'local' } } const drive = getGuildDrive(this.storagePath, row.guildId) const localDrive = await drive.get(row.id, row.filename, row.drivePath) - if (localDrive?.length) return localDrive - - let buf = null - if (this.guild) { - buf = await this.guild.fetchAttachmentFromPeers(attachmentId).catch(() => null) + if (localDrive?.length) { + return { buf: localDrive, source: 'drive-local' } } - if (!buf && isHexDriveKey(row.driveKey) && (this.guild?.peers?.size || 0) > 0) { + + let buf = await this._fetchAttachMeshWithRetry(attachmentId) + if (buf?.length) { + await this.attachments.persistFetched(row, buf) + return { buf, source: 'attach-mesh' } + } + if (isHexDriveKey(row.driveKey) && (this.guild?.peers?.size || 0) > 0) { buf = await drive.fetchRemote({ driveKey: row.driveKey, drivePath: row.drivePath, @@ -2147,9 +2159,16 @@ class PearcordPlatform extends EventEmitter { swarm: this.guild?.swarm, timeoutMs: 8000 }) + if (buf?.length) { + await this.attachments.persistFetched(row, buf) + return { buf, source: 'hyperdrive' } + } } - if (!buf) throw new Error('attachment file missing') - await this.attachments.persistFetched(row, buf) + throw new Error('attachment file missing') + } + + async readAttachmentBytes (attachmentId) { + const { buf } = await this.readAttachmentBytesWithSource(attachmentId) return buf } @@ -2159,12 +2178,13 @@ class PearcordPlatform extends EventEmitter { if (!String(row.mimeType || '').startsWith('image/')) { throw new Error('preview only for images') } - const buf = await this.readAttachmentBytes(attachmentId) + const { buf, source } = await this.readAttachmentBytesWithSource(attachmentId) return { attachmentId: row.id, mimeType: row.mimeType, filename: row.filename, - dataBase64: b4a.toString(buf, 'base64') + dataBase64: b4a.toString(buf, 'base64'), + fetchSource: source } }