mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-07-27 20:29:10 +00:00
ed49ed6165
compression@1.8.1's negotiator silently fails to set the Content-Encoding response header when Accept-Encoding carries an unknown token like zstd, even though the body is still compressed with brotli. Chromium 123+ sends "Accept-Encoding: zstd, gzip, br" by default, including Playwright's bundled Chromium over plain HTTP, so any homelab Sencho viewed without a TLS terminator triggered ERR_CONTENT_DECODING_FAILED in the browser and rendered as a blank page. Insert a tiny middleware (step 4 of the canonical pipeline) that drops zstd tokens from Accept-Encoding before compression's negotiator runs. The negotiator then picks br or gzip and writes the matching header. The fix is no-op when zstd is absent. If only zstd was offered, the header falls back to identity so the response is served uncompressed. Renumber the canonical-order JSDoc in app.ts from 17 to 18 steps and update the step-number references in hub-only-guard and proxy-mount-order test docstrings to match. 11 new tests (8 unit + 3 supertest integration against real compression with a 3000-byte body) lock in the behavior: zstd-stripping across casing and q-values, surviving tokens preserved verbatim, identity fallback when only zstd was offered, Content-Encoding header always written when at least one supported codec remains.
25 lines
1.1 KiB
TypeScript
25 lines
1.1 KiB
TypeScript
import type { RequestHandler } from 'express';
|
|
|
|
// compression@1.8.1's encoding negotiation (via negotiator@1.0.0) silently
|
|
// breaks when Accept-Encoding carries an unknown token such as `zstd`, which
|
|
// Chromium 123+ sends by default and which Playwright's bundled Chromium sends
|
|
// even over plain HTTP. In that path the response body is still compressed
|
|
// (brotli) but the Content-Encoding response header is never set, so the
|
|
// browser decode-fails with ERR_CONTENT_DECODING_FAILED and the Sencho UI
|
|
// renders blank. Drop the offending token before compression sees it so the
|
|
// negotiator falls back to a supported encoding and the header is written.
|
|
export const normalizeAcceptEncoding: RequestHandler = (req, _res, next) => {
|
|
const raw = req.headers['accept-encoding'];
|
|
if (typeof raw !== 'string' || !/\bzstd\b/i.test(raw)) {
|
|
next();
|
|
return;
|
|
}
|
|
const filtered = raw
|
|
.split(',')
|
|
.map((s) => s.trim())
|
|
.filter((s) => s.length > 0 && !/^zstd(\s*;\s*q\s*=.*)?$/i.test(s))
|
|
.join(', ');
|
|
req.headers['accept-encoding'] = filtered || 'identity';
|
|
next();
|
|
};
|