Skip to content

feat(weixin): add WebUI QR-code login via SSE#1744

Merged
piorpua merged 12 commits intoiOfficeAI:mainfrom
JerryLiu369:feat/weixin-webui-login
Mar 28, 2026
Merged

feat(weixin): add WebUI QR-code login via SSE#1744
piorpua merged 12 commits intoiOfficeAI:mainfrom
JerryLiu369:feat/weixin-webui-login

Conversation

@JerryLiu369
Copy link
Copy Markdown
Member

Summary

  • WebUI mode cannot use Electron IPC (window.electronAPI is undefined), so the existing WeChat login button silently failed
  • Adds GET /api/channel/weixin/login SSE endpoint that drives the existing WeixinLogin.ts flow and streams qr / scanned / done / error events to the browser
  • Frontend detects WebUI mode and uses EventSource + QRCodeSVG (from qrcode.react) to display the QR code
  • QR code encodes the qrcode_img_content page URL (not the raw hex ticket) — confirmed by reading the WeChat liteapp JS bundle which calls Lr.toCanvas(canvas, window.location.href, ...)
  • Also exposes qrcodeData as a second argument of the onQR callback so both Electron (canvas render) and WebUI (QRCodeSVG) paths have what they need; Electron behaviour is unchanged

Test plan

  • Open WebUI in a browser (not Electron)
  • Go to Settings → Channels → WeChat → click Login
  • Confirm a QR code renders (not a broken image or hex string)
  • Scan with WeChat mobile — should prompt to authorise the bot
  • After scan, status changes to "scanned" then login completes
  • Verify Electron login flow still works normally

🤖 Generated with Claude Code

@sentry
Copy link
Copy Markdown

sentry bot commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 65.11628% with 30 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ttingsModal/contents/channels/WeixinConfigForm.tsx 60.29% 19 Missing and 8 partials ⚠️
src/process/webserver/routes/weixinLoginRoutes.ts 87.50% 2 Missing ⚠️
src/process/webserver/routes/apiRoutes.ts 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@JerryLiu369 JerryLiu369 force-pushed the feat/weixin-webui-login branch 3 times, most recently from e5e5905 to a1cc579 Compare March 26, 2026 16:39
@piorpua piorpua added bot:reviewing Review in progress (mutex) and removed bot:reviewing Review in progress (mutex) labels Mar 27, 2026
JerryLiu369 and others added 10 commits March 28, 2026 00:12
WebUI mode cannot call Electron IPC (window.electronAPI is undefined),
so add a server-sent events endpoint GET /api/channel/weixin/login that
drives the existing WeixinLogin flow and streams qr/scanned/done/error
events to the browser.

The QR code encodes the qrcode_img_content page URL (not the raw hex
ticket) — confirmed by reading the WeChat liteapp JS bundle which does
Lr.toCanvas(canvas, window.location.href, ...).

Also expose qrcodeData as second arg of onQR callback so both Electron
(canvas render) and WebUI (QRCodeSVG) paths have what they need.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
onQR now receives (pageUrl, qrcodeData) — update existing tests to
match the new signature.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
After disablePlugin, the backend emits pluginStatusChanged asynchronously
(emitStatusChange is not awaited), so the event arrives after the IPC
response. The event still carries hasToken: true (enabled: false), which
caused the useEffect to flip loginState back to 'connected'.

Fix: require pluginStatus?.enabled to be true before syncing to
connected state, and apply the same guard in renderLoginArea.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
useState init used hasToken alone, so a disabled plugin with stored
credentials would show 'connected' on page open.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@JerryLiu369 JerryLiu369 force-pushed the feat/weixin-webui-login branch from 6df399d to 68dcaaa Compare March 27, 2026 16:12
@piorpua piorpua added the bot:reviewing Review in progress (mutex) label Mar 28, 2026
@piorpua
Copy link
Copy Markdown
Contributor

piorpua commented Mar 28, 2026

Code Review:feat(weixin): add WebUI QR-code login via SSE (#1744)

变更概述

本 PR 为 WebUI 模式(浏览器无 electronAPI)添加了微信扫码登录支持:在主进程新增 SSE 路由 /api/channel/weixin/login,驱动现有 WeixinLogin.ts 流程并向前端推送 qr/scanned/done/error 事件;前端检测 WebUI 环境并使用 EventSource + QRCodeSVG 渲染二维码。同时补充了断开连接功能,并修复了 hasToken=true, enabled=false 时误显示已连接的 bug。


方案评估

结论:✅ 方案合理

整体方案与项目架构一致:SSE 路由放在 src/process/webserver/routes/,前端通过运行时检测 electronAPI 分支,不影响 Electron 路径。将 qrcode_img_content(页面 URL)直接作为 QR 编码内容符合微信 liteapp 的实际行为(在 PR 描述中有验证)。enableWeixinPlugin 抽离复用 Electron 和 WebUI 两条路径,逻辑清晰,无不必要的抽象。


问题清单

🟡 MEDIUM — EventSource 未在组件卸载时关闭,存在连接泄漏

文件src/renderer/components/settings/SettingsModal/contents/channels/WeixinConfigForm.tsx,第 277 行

问题代码

const handleLoginWebUI = () => {
  ...
  const es = new EventSource('/api/channel/weixin/login', { withCredentials: true });
  // es 仅保存在局部变量中,组件卸载时无法关闭
  ...
};

问题说明:如果用户在登录流程中关闭 Settings 弹窗,EventSource 连接不会被关闭,服务端的 startLogin 流程继续运行,SSE 连接保持存活直至超时。同时 React 的 state setter 仍会被回调,可能触发无副作用但不必要的状态更新。

修复建议:将 EventSource 存入 useRef,并在 useEffect 清理函数中关闭:

const eventSourceRef = useRef<EventSource | null>(null);

// handleLoginWebUI 中:
const es = new EventSource('/api/channel/weixin/login', { withCredentials: true });
eventSourceRef.current = es;

// 组件卸载清理:
useEffect(() => {
  return () => {
    eventSourceRef.current?.close();
    eventSourceRef.current = null;
  };
}, []);

🟡 MEDIUM — 补丁覆盖率 60.29%,低于项目 80% 下限

文件src/renderer/components/settings/SettingsModal/contents/channels/WeixinConfigForm.tsx

问题说明:Codecov 报告补丁覆盖率 60.29%(19 行未覆盖 + 8 行部分覆盖),未达到项目 ≥80% 的覆盖率要求。主要缺口包括:

  • enableWeixinPlugin 失败路径(success=false
  • handleLoginWebUI 中 error 事件的 expired/too many 分支
  • onerror 回调
  • handleDisconnect 错误路径

修复建议:补充以下测试场景:

// enableWeixinPlugin 失败路径
mockEnablePlugin.mockResolvedValueOnce({ success: false, msg: 'Error msg' });
// → 应显示错误 Message,loginState 回到 'idle'

// SSE error 事件中 'expired' 分支
es.emit('error', { message: 'QR code expired' });
// → 应显示 warning,loginState 回到 'idle'

// handleDisconnect 失败路径
mockDisablePlugin.mockResolvedValueOnce({ success: false, msg: 'Disable failed' });
// → 应显示错误 Message

🔵 LOW — es.onerroraddEventListener('error') 重复处理连接级错误(oxlint 警告)

文件src/renderer/components/settings/SettingsModal/contents/channels/WeixinConfigForm.tsx,第 300–316 行

问题代码

es.addEventListener('error', (e: MessageEvent) => {
  es.close();
  // ...处理服务端发送的 error 事件或连接错误
});

es.onerror = () => {           // oxlint: prefer-add-event-listener
  es.close();
  setLoginState('idle');
  setQrcodeDataUrl(null);
};

问题说明:连接级错误会同时触发 addEventListener('error')onerror,导致 es.close() 和状态重置被调用两次。oxlint 报告 prefer-add-event-listener 违规。

修复建议:删除 onerror 赋值,addEventListener('error') 已通过 e.data ? ... : '' 检查兼容处理两种情形(服务端 error 事件和连接级错误)。


🔵 LOW — 变量注释描述不准确

文件src/renderer/components/settings/SettingsModal/contents/channels/WeixinConfigForm.tsx,第 64 行

问题代码

// In Electron mode this holds a base64 data URL; in WebUI mode it holds the raw QR ticket string.

问题说明:WebUI 模式下存储的是 qrcode_img_content 页面 URL(非 raw ticket)。

修复建议

// In Electron mode this holds a base64 data URL; in WebUI mode it holds the qrcode_img_content page URL.

汇总

# 严重级别 文件 问题
1 🟡 MEDIUM WeixinConfigForm.tsx:277 EventSource 未在组件卸载时关闭
2 🟡 MEDIUM WeixinConfigForm.tsx 补丁覆盖率 60.29%,低于 80% 下限
3 🔵 LOW WeixinConfigForm.tsx:312 onerror 与 addEventListener 重复
4 🔵 LOW WeixinConfigForm.tsx:64 注释描述不准确

结论

⚠️ 有条件批准 — 无阻塞性问题,EventSource 未清理和覆盖率不足需补充修复后可合并。


本报告由本地 pr-review skill 生成,包含完整项目上下文,无截断限制。

@piorpua piorpua added bot:ready-to-fix CONDITIONAL review done, waiting for bot fix bot:fixing Fix in progress (mutex) and removed bot:reviewing Review in progress (mutex) bot:ready-to-fix CONDITIONAL review done, waiting for bot fix labels Mar 28, 2026
- Store EventSource in a ref and close it in useEffect cleanup to prevent
  connection leaks when Settings modal is closed during WebUI QR login
- Add tests for enableWeixinPlugin failure path, SSE error event branches
  (expired and non-expired), handleDisconnect failure path, and unmount cleanup

Review follow-up for iOfficeAI#1744
@piorpua
Copy link
Copy Markdown
Contributor

piorpua commented Mar 28, 2026

PR Fix 验证报告

原始 PR: #1744
修复方式: 直接推送到 feat/weixin-webui-login

# 严重级别 文件 问题 修复方式 状态
1 🟡 MEDIUM WeixinConfigForm.tsx:277 EventSource 未在组件卸载时关闭 添加 eventSourceRef,通过 useEffect 清理函数在组件卸载时关闭 EventSource ✅ 已修复
2 🟡 MEDIUM WeixinConfigForm.tsx 补丁覆盖率 60.29%,低于 80% 下限 新增 5 个测试:enableWeixinPlugin 失败路径、SSE error 事件两个分支(expired/non-expired)、handleDisconnect 失败路径、组件卸载清理 ✅ 已修复

总结: ✅ 已修复 2 个 | ❌ 未能修复 0 个

🔵 LOW 级别问题已跳过(不阻塞合并,修复优先级低)。

@piorpua piorpua added bot:reviewing Review in progress (mutex) and removed bot:fixing Fix in progress (mutex) bot:reviewing Review in progress (mutex) labels Mar 28, 2026
@piorpua
Copy link
Copy Markdown
Contributor

piorpua commented Mar 28, 2026

Code Review:feat(weixin): add WebUI QR-code login via SSE (#1744)

变更概述

本 PR 为 WebUI 模式下的微信登录增加了 SSE(Server-Sent Events)通道。核心改动包括:新增 /api/channel/weixin/login SSE 端点(weixinLoginRoutes.ts),扩展 LoginCallbacks.onQR 签名以暴露原始二维码 ticket,在 WeixinConfigForm 中检测 WebUI 模式并通过 EventSource + QRCodeSVG 展示登录二维码,同时补充了对应的单元测试。


方案评估

结论:✅ 方案合理

方案正确区分了 Electron(IPC + 隐藏窗口截图)和 WebUI(SSE + QRCodeSVG)两条路径,对已有 Electron 流程没有破坏性改动。SSE 端点做了认证中间件(validateApiAccess)保护,req.on('close', handle.abort) 确保客户端断开后服务端登录流程也会终止。将 qrcode_img_content 页面 URL 而非原始 ticket 作为二维码内容,与 PR 描述中对微信 liteapp JS bundle 的验证结论一致,方案合理。


问题清单

🔵 LOW — es.onerrores.addEventListener('error', ...) 双重注册导致重复处理

文件src/renderer/components/settings/SettingsModal/contents/channels/WeixinConfigForm.tsx,第 310–326 行

问题说明EventSource 规范中 onerroraddEventListener('error', ...) 均监听同一 error 事件类型(网络断开和服务端发送的 event: error 消息都会触发两者)。当前实现在任何错误场景下都会执行两次 state 重置,属于冗余处理。addEventListener('error', ...) 里已通过 e.data ? ... : '' 分支处理了无数据的网络错误,onerror 可以安全移除。此问题也正是 oxlint (prefer-add-event-listener) 警告的根源。

修复建议:移除 es.onerror,保留 addEventListener('error', ...) 单一处理即可。


🔵 LOW — weixinLoginRoutes.test.ts 未覆盖 onError 路径

文件tests/unit/weixinLoginRoutes.test.ts

问题说明:当前测试只验证了 qr → scanned → done 的正常链路,未覆盖 onError 回调(SSE 发送 event: errorres.end())。Codecov 报告显示 weixinLoginRoutes.ts 缺少 2 行覆盖(87.5%,仍高于 80% 阈值,CI 通过)。


汇总

# 严重级别 文件 问题
1 🔵 LOW WeixinConfigForm.tsx:322 onerroraddEventListener('error', ...) 双重注册
2 🔵 LOW weixinLoginRoutes.test.ts 未覆盖 onError 路径

结论

批准合并 — 无阻塞性问题。核心功能正确,Electron 路径不受影响,测试覆盖率满足项目要求(CI 全部通过)。上述两个 LOW 级别问题可在后续 PR 中跟进修复。


本报告由本地 pr-review skill 生成,包含完整项目上下文,无截断限制。

@piorpua
Copy link
Copy Markdown
Contributor

piorpua commented Mar 28, 2026

✅ 已自动 review,无阻塞性问题,正在触发自动合并。

@piorpua piorpua merged commit 133c22b into iOfficeAI:main Mar 28, 2026
12 of 13 checks passed
@piorpua piorpua added bot:done Auto-merged by bot and removed bot:reviewing Review in progress (mutex) labels Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:done Auto-merged by bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants