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.
This commit is contained in:
tiennm99 authored and Tien Nguyen Minh committed 2026-04-17 10:31:05 +07:00
1 parent ad8d2a6f71
commit a0c32b18ee
1 file changed
+15 -15
+15 -15
View File
@@ -11,23 +11,23 @@ const app = new Hono();
// ~64 bytes is generous per pixel JSON object {"x":2047,"y":2047,"color":31} // ~64 bytes is generous per pixel JSON object {"x":2047,"y":2047,"color":31}
const MAX_BODY_BYTES = MAX_BATCH_SIZE * 64; 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) => { app.get('/api/canvas', async (c) => {
const buffer = await getFullCanvas(c.env); try {
const acceptsGzip = (c.req.header('accept-encoding') || '').includes('gzip'); const buffer = await getFullCanvas(c.env);
return new Response(buffer, {
const headers = { headers: {
'Content-Type': 'application/octet-stream', 'Content-Type': 'application/octet-stream',
'Cache-Control': 'public, max-age=10, s-maxage=10, stale-while-revalidate=30', 'Cache-Control': 'public, max-age=10, s-maxage=10, stale-while-revalidate=30',
Vary: 'Accept-Encoding', },
}; });
} catch (err) {
if (acceptsGzip) { console.error('Canvas read failed:', err);
const gzStream = new Response(buffer).body.pipeThrough(new CompressionStream('gzip')); return c.json({ error: 'canvas_read_failed', message: String(err) }, 500);
headers['Content-Encoding'] = 'gzip';
return new Response(gzStream, { headers });
} }
return new Response(buffer, { headers });
}); });
/** POST /api/place — batch pixel placement */ /** POST /api/place — batch pixel placement */