From a0c32b18eef998d15facb0873892e70a16ad72b5 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Fri, 17 Apr 2026 10:30:33 +0700 Subject: [PATCH] fix: serve /api/canvas raw, drop manual gzip + add error envelope Manual CompressionStream + Content-Encoding: gzip caused the browser to receive un-decoded gzipped bytes through the wrangler-dev + vite proxy path, surfacing as "Canvas buffer truncated: got 3598 bytes" in the new decoder bounds check. Cloudflare's edge auto-compresses compressible content already, so the manual gzip layer was redundant in prod and broken in dev. Also wraps getFullCanvas in try/catch and returns a JSON error envelope on failure instead of letting Hono's default handler return a 500 with text body the client decoder would mis-parse. --- src/worker.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/worker.js b/src/worker.js index d352e00..98a9a68 100644 --- a/src/worker.js +++ b/src/worker.js @@ -11,23 +11,23 @@ const app = new Hono(); // ~64 bytes is generous per pixel JSON object {"x":2047,"y":2047,"color":31} const MAX_BODY_BYTES = MAX_BATCH_SIZE * 64; -/** GET /api/canvas — full canvas as binary; gzip when supported */ +/** GET /api/canvas — full canvas as binary. + * Cloudflare's edge auto-compresses compressible content; we don't set + * Content-Encoding manually (caused double-encoding / undecoded blobs + * through wrangler dev + vite proxy during testing). */ app.get('/api/canvas', async (c) => { - const buffer = await getFullCanvas(c.env); - const acceptsGzip = (c.req.header('accept-encoding') || '').includes('gzip'); - - const headers = { - 'Content-Type': 'application/octet-stream', - 'Cache-Control': 'public, max-age=10, s-maxage=10, stale-while-revalidate=30', - Vary: 'Accept-Encoding', - }; - - if (acceptsGzip) { - const gzStream = new Response(buffer).body.pipeThrough(new CompressionStream('gzip')); - headers['Content-Encoding'] = 'gzip'; - return new Response(gzStream, { headers }); + try { + const buffer = await getFullCanvas(c.env); + return new Response(buffer, { + headers: { + 'Content-Type': 'application/octet-stream', + 'Cache-Control': 'public, max-age=10, s-maxage=10, stale-while-revalidate=30', + }, + }); + } catch (err) { + console.error('Canvas read failed:', err); + return c.json({ error: 'canvas_read_failed', message: String(err) }, 500); } - return new Response(buffer, { headers }); }); /** POST /api/place — batch pixel placement */