From 5047527b478fc8d08d77d8f6522c8ee34b3e3800 Mon Sep 17 00:00:00 2001 From: Anish Sarkar <104695310+AnishSarkar22@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:30:43 +0530 Subject: [PATCH] feat: enhance binary attachment handling in sync engine --- surfsense_obsidian/src/sync-engine.ts | 36 ++++++++++++++++++++++++++- surfsense_obsidian/src/types.ts | 6 +++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/surfsense_obsidian/src/sync-engine.ts b/surfsense_obsidian/src/sync-engine.ts index 4ffd2a651..ccf0c485f 100644 --- a/surfsense_obsidian/src/sync-engine.ts +++ b/surfsense_obsidian/src/sync-engine.ts @@ -368,10 +368,12 @@ export class SyncEngine { private async buildBinaryPayload(file: TFile, vaultId: string): Promise { // Attachments skip buildNotePayload (no markdown metadata) but still - // need hash + stat so the server can de-dupe and manifest diff works. + // need raw bytes + hash + stat so the backend can ETL-extract text + // and manifest diff still works. const buf = await this.deps.app.vault.readBinary(file); const digest = await crypto.subtle.digest("SHA-256", buf); const hash = bufferToHex(digest); + const binaryBase64 = arrayBufferToBase64(buf); return { vault_id: vaultId, path: file.path, @@ -390,6 +392,8 @@ export class SyncEngine { mtime: file.stat.mtime, ctime: file.stat.ctime, is_binary: true, + binary_base64: binaryBase64, + mime_type: mimeTypeFromExtension(file.extension), }; } @@ -584,6 +588,36 @@ function bufferToHex(buf: ArrayBuffer): string { return hex; } +function arrayBufferToBase64(buf: ArrayBuffer): string { + const bytes = new Uint8Array(buf); + const chunkSize = 0x8000; + let binary = ""; + for (let i = 0; i < bytes.length; i += chunkSize) { + const chunk = bytes.subarray(i, i + chunkSize); + binary += String.fromCharCode(...Array.from(chunk)); + } + return btoa(binary); +} + +function mimeTypeFromExtension(extension: string): string | undefined { + const ext = extension.toLowerCase(); + const mimeByExt: Record = { + pdf: "application/pdf", + png: "image/png", + jpg: "image/jpeg", + jpeg: "image/jpeg", + gif: "image/gif", + webp: "image/webp", + svg: "image/svg+xml", + txt: "text/plain", + csv: "text/csv", + docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation", + xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }; + return mimeByExt[ext]; +} + function formatRelative(ts: number): string { const diff = Date.now() - ts; if (diff < 60_000) return "just now"; diff --git a/surfsense_obsidian/src/types.ts b/surfsense_obsidian/src/types.ts index 97ddcdbdf..7f6c42723 100644 --- a/surfsense_obsidian/src/types.ts +++ b/surfsense_obsidian/src/types.ts @@ -85,6 +85,12 @@ export interface NotePayload { size: number; mtime: number; ctime: number; + /** Non-markdown attachment marker; enables backend ETL path. */ + is_binary?: boolean; + /** Base64-encoded file bytes for binary attachments. */ + binary_base64?: string; + /** Optional MIME type hint for backend parsers. */ + mime_type?: string; [key: string]: unknown; }