From 364c99381c6649ff5afd6ea3ce9b1fd1a96e5dcb Mon Sep 17 00:00:00 2001 From: beo3000 Date: Sat, 21 Feb 2026 19:44:03 +0100 Subject: [PATCH] fix: chunked base64 encode/decode for large image blobs in sync --- ka-note/VERSION | 2 +- ka-note/client/src/lib/sync/syncService.ts | 20 +++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ka-note/VERSION b/ka-note/VERSION index fd8b4f0..4686f75 100644 --- a/ka-note/VERSION +++ b/ka-note/VERSION @@ -1 +1 @@ -1.0.12 \ No newline at end of file +1.0.13 \ No newline at end of file diff --git a/ka-note/client/src/lib/sync/syncService.ts b/ka-note/client/src/lib/sync/syncService.ts index 3d2c17b..b889994 100644 --- a/ka-note/client/src/lib/sync/syncService.ts +++ b/ka-note/client/src/lib/sync/syncService.ts @@ -41,9 +41,7 @@ async function pullAndMerge(since: Date | null): Promise { if (!res.ok) throw new Error(`Pull failed: ${res.status}`); const data: SyncPullResponse = await res.json(); - const { contexts, topics, historyEntries, ratings } = data.changes; - - const { imageBlobs } = data.changes; + const { contexts, topics, historyEntries, ratings, imageBlobs } = data.changes; await db.transaction('rw', [db.contexts, db.topics, db.historyEntries, db.ratings, db.imageBlobs], async () => { for (const serverCtx of contexts) { @@ -73,8 +71,10 @@ async function pullAndMerge(since: Date | null): Promise { for (const serverBlob of (imageBlobs ?? [])) { const local = await db.imageBlobs.get(serverBlob.id); if (!local || serverBlob.version > local.version) { - // Decode base64 → Blob - const bytes = Uint8Array.from(atob(serverBlob.data), (c) => c.charCodeAt(0)); + // Decode base64 → Blob (chunked to avoid stack overflow on large blobs) + const binary = atob(serverBlob.data); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i); const blobData = new Blob([bytes], { type: serverBlob.mimeType }); await db.imageBlobs.put({ id: serverBlob.id, @@ -102,11 +102,17 @@ async function pushAll(): Promise { db.imageBlobs.toArray(), ]); - // Encode Blob → base64 for transport + // Encode Blob → base64 for transport (chunked to avoid stack overflow on large blobs) const imageBlobs = await Promise.all( localBlobs.map(async (b) => { const buf = await b.data.arrayBuffer(); - const base64 = btoa(String.fromCharCode(...new Uint8Array(buf))); + const bytes = new Uint8Array(buf); + let binary = ''; + const CHUNK = 8192; + for (let i = 0; i < bytes.length; i += CHUNK) { + binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK)); + } + const base64 = btoa(binary); return { id: b.id, mimeType: b.mimeType,