From 02077baa07ef32ac337ebf0de28644363843c0f4 Mon Sep 17 00:00:00 2001 From: CHYMZXS <834639376@qq.com> Date: Fri, 2 Oct 2026 12:16:44 +0800 Subject: [PATCH] =?UTF-8?q?fix(proxy):=20=E5=AF=B9=20HTML=20=E5=AF=BC?= =?UTF-8?q?=E8=88=AA=E8=AF=B7=E6=B1=82=E5=B8=A6=20accept-encoding:=20ident?= =?UTF-8?q?ity=EF=BC=8C=E8=AE=A9=20polyfill=20=E6=B3=A8=E5=85=A5=E7=9C=9F?= =?UTF-8?q?=E6=AD=A3=E8=90=BD=E5=9C=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HTML 注入分支带 !isCompressed(...) 条件,而浏览器导航必然携带 Accept-Encoding,dsh web 于是返回 gzip 压缩文档 → 注入永不命中。 data-dsh-pocket-polyfill(crypto.randomUUID / AbortSignal.any)与 data-dsh-pocket-transport-shim 对所有真实浏览器都是死代码, issue #53 / #96 的修复因此从未生效。 缺 AbortSignal.any 的内核(Android WebView / 厂商浏览器 < Chrome 116) 在连接层直接抛 TypeError,/api/remote.mux 建链后立即断开,页面永久停在 「重新连接中」(connection.connecting)。 修复:HTML 导航请求向上游要 identity,注入即可落地。上游若忽略 identity,isCompressed 分支照旧原样透传压缩流,无损坏风险; JS/CSS 等子资源的压缩透传不受影响。 测试: - 新增「HTML 导航要 identity 并完成注入」(假上游尊重 Accept-Encoding) - 新增「上游忽略 identity 时压缩流仍原样透传」 - 既有「压缩 HTML 不注入」用例改为明确覆盖上述兜底路径 Closes #150 --- lib/proxy.mjs | 9 +++++ test/proxy.test.js | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/lib/proxy.mjs b/lib/proxy.mjs index 8e29df1..f85f240 100644 --- a/lib/proxy.mjs +++ b/lib/proxy.mjs @@ -841,6 +841,15 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE } } const headers = loopbackAuthority({ ...req.headers }, upstream); + // HTML 导航请求向上游要**未压缩**文档(issue #150)。 + // 浏览器导航必然带 Accept-Encoding,dsh web 于是 gzip 压缩 HTML,下面「只给未压缩 + // HTML 注入」的分支因此永不命中 —— data-dsh-pocket-polyfill(randomUUID / + // AbortSignal.any)与 data-dsh-pocket-transport-shim 对真实浏览器全是死代码。 + // 后果:缺 AbortSignal.any 的内核(Android WebView / 厂商浏览器 < Chrome 116) + // 在连接层抛 TypeError,/api/remote.mux 建链即断,页面永久停在「重新连接中」。 + // 上游若忽略 identity,isCompressed 分支照旧原样透传压缩流,无损坏风险; + // JS/CSS 等子资源的压缩透传不受影响(只对 HTML 导航生效)。 + if (injectHtml && isHtmlRequest(req)) headers['accept-encoding'] = 'identity'; // dsh web 浏览器会话 token(issue #77):首屏根路径补一次,换回绑定 authority 的 cookie const launchTok = (typeof launchToken === 'function' ? launchToken() : '') || ''; // 先清掉历史遗留的 dsh-desktop-* 参数(issue #75),再补 launch token diff --git a/test/proxy.test.js b/test/proxy.test.js index 77fe8c3..7a13505 100644 --- a/test/proxy.test.js +++ b/test/proxy.test.js @@ -393,9 +393,13 @@ test('会话指纹已移除(2.10.0):页面与登录页不再注入指纹/a }); test('压缩 HTML(gzip)不注入 polyfill——防止损坏压缩流', async () => { + // 注意(issue #150):代理现在对 HTML 导航请求带 accept-encoding: identity, + // 正常 dsh web 会因此回未压缩文档。本用例覆盖的是**上游忽略 identity** 的兜底路径: + // 上游照旧回 gzip 时,代理必须原样透传、绝不注入,否则会损坏压缩流。 const zlib = await import('node:zlib'); const http = await import('node:http'); const up = createServer((req, res) => { + // 无条件 gzip:模拟不认 identity 的上游 res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'content-encoding': 'gzip' }); res.end(zlib.gzipSync('
compressed-page')); }); @@ -422,6 +426,95 @@ test('压缩 HTML(gzip)不注入 polyfill——防止损坏压缩流', async } }); +test('issue #150:HTML 导航请求向上游要 identity,注入对真实浏览器生效', async () => { + // 真实浏览器导航必然带 Accept-Encoding,dsh web 于是 gzip 压缩 HTML。 + // 本用例的假上游**尊重 Accept-Encoding**(像 dsh web 一样),用于证明代理现在 + // 会主动要 identity,从而拿到未压缩文档并完成注入。 + const zlib = await import('node:zlib'); + const http = await import('node:http'); + const seenAcceptEncoding = []; + const up = createServer((req, res) => { + seenAcceptEncoding.push(req.headers['accept-encoding'] ?? '(none)'); + const ae = String(req.headers['accept-encoding'] ?? ''); + const html = Buffer.from('