From 86747cf4ab0d12144aac97178aaa03d0fc2ef965 Mon Sep 17 00:00:00 2001 From: "tbh9tjm5acysj@aliyun.com" Date: Thu, 25 Jun 2026 11:34:06 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20/emulate=20?= =?UTF-8?q?=E7=AB=AF=E7=82=B9=E6=94=AF=E6=8C=81=E8=AE=BE=E5=A4=87/?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E8=A7=86=E5=8F=A3=E6=A8=A1=E6=8B=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CDP proxy 增加 POST /emulate(Emulation.setDeviceMetricsOverride + 触摸模拟 + UA 覆盖), ?reset=1 清除覆盖。用于移动端 UI 验证(触发 matchMedia 断点、touch 检测、移动 UA), 免去每次手写 CDP 脚本连 browser 端点。override 作用于目标 session,emulate 后其 /eval /screenshot /click 保持该视口,reset 或 /close 即还原;纯新增路由,不影响现有端点。 --- scripts/cdp-proxy.mjs | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/scripts/cdp-proxy.mjs b/scripts/cdp-proxy.mjs index 9457405..1446faa 100755 --- a/scripts/cdp-proxy.mjs +++ b/scripts/cdp-proxy.mjs @@ -584,6 +584,35 @@ const server = http.createServer(async (req, res) => { res.end(resp.result?.result?.value || '{}'); } + // POST /emulate?target=xxx (body=JSON) - 设备/移动视口模拟;?reset=1 清除 + // body: {"width":390,"height":844,"dsf":2,"mobile":true,"ua":"...iPhone..."} + // 状态作用于该 target 的 session:emulate 后其 /eval /screenshot /click 均保持该视口, + // 直到 reset 或 /close。建议只对自建临时 tab 使用。 + else if (pathname === '/emulate') { + const sid = await ensureSession(q.target); + if (q.reset === '1' || q.reset === 'true') { + await sendCDP('Emulation.clearDeviceMetricsOverride', {}, sid); + await sendCDP('Emulation.setTouchEmulationEnabled', { enabled: false }, sid); + await sendCDP('Emulation.setUserAgentOverride', { userAgent: '' }, sid); + res.end(JSON.stringify({ reset: true })); + } else { + let opts = {}; + const body = (await readBody(req)).trim(); + if (body) { + try { opts = JSON.parse(body); } + catch { res.statusCode = 400; res.end(JSON.stringify({ error: 'body 需为 JSON' })); return; } + } + const width = opts.width || 390; + const height = opts.height || 844; + const deviceScaleFactor = opts.dsf ?? opts.deviceScaleFactor ?? 2; + const mobile = opts.mobile ?? true; + await sendCDP('Emulation.setDeviceMetricsOverride', { width, height, deviceScaleFactor, mobile }, sid); + await sendCDP('Emulation.setTouchEmulationEnabled', { enabled: !!mobile }, sid); + if (opts.ua) await sendCDP('Emulation.setUserAgentOverride', { userAgent: opts.ua }, sid); + res.end(JSON.stringify({ emulated: { width, height, deviceScaleFactor, mobile, ua: !!opts.ua } })); + } + } + else { res.statusCode = 404; res.end(JSON.stringify({ @@ -600,6 +629,7 @@ const server = http.createServer(async (req, res) => { '/click?target=': 'POST body=CSS选择器 - 点击元素', '/scroll?target=&y=&direction=': 'GET - 滚动页面', '/screenshot?target=&file=': 'GET - 截图', + '/emulate?target=&reset=': 'POST body=JSON{width,height,dsf,mobile,ua} - 设备/移动视口模拟;reset=1 清除', }, })); }