Skip to content

feat(about): 关于页展示 Web 引擎与 WebView 完整版本号(桌面 + 移动端) - #773

Open
k6G52m4Dz75W wants to merge 17 commits into
codedogQBY:mainfrom
k6G52m4Dz75W:feat/webview-version
Open

feat(about): 关于页展示 Web 引擎与 WebView 完整版本号(桌面 + 移动端)#773
k6G52m4Dz75W wants to merge 17 commits into
codedogQBY:mainfrom
k6G52m4Dz75W:feat/webview-version

Conversation

@k6G52m4Dz75W

@k6G52m4Dz75W k6G52m4Dz75W commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

动机

阅读器的多个能力依赖 WebView 引擎及其版本:@layer/:has() 的兼容线
(WebView2 ≥ 105、WebKitGTK ≥ 2.36)、execCommand 行为、剪贴板权限策略等
各不相同。用户反馈"某个功能不生效"时,我们需要知道确切的引擎和构建号
而关于页此前只有应用版本号。本 PR 在桌面端与移动端的关于页都加上 Web 引擎 +
完整版本号的展示与一键复制,并把同样的信息汇入用户反馈的设备信息里。

桌面端

  • 版本卡片新增"WebView 版本"行,按平台解析 UA:
    • Windows → WebView2Edg/ 令牌)
    • macOS/iOS → WebKitVersion/ 令牌,跟随系统 WebKit)
    • Android → Android WebViewChrome/ 令牌 + ; wv) 标记)
    • Linux → WebKitGTK(UA 令牌冻结、拿不到真实版本,如实不显示数字)
  • 绕过 UA Reduction 拿完整构建号:Chromium 已把 UA 中的次版本冻结为 0
    Edg/152.0.0.0),通过 User-Agent Client Hints 的 fullVersionList
    getHighEntropyValues)获取真实完整版本,如 WebView2 152.0.4191.62
  • 版本卡片常驻复制按钮:一键复制两行(应用版本 + WebView 版本),直接
    粘贴进 issue;成功后图标变对勾 + toast 提示,失败有错误 toast。

移动端(Expo)

  • RN 层没有真实 UA(App.tsx 为 LangChain 垫了 "ReactNative" 占位),因此:
    • reader.html 启动时经现有桥回传自身 UA(正是渲染书籍的系统 WebView),
      并通过 Client Hints 附带完整构建号(reader 页是安全上下文,可读高熵值);
    • 另有 0×0 隐藏探针 WebView 在应用启动时立即回传(同样带 Client Hints),
      5 秒超时或加载失败自动卸载——无需打开任何书即可显示
  • 关于页的应用版本号下方直接显示引擎 + 完整构建号(无标题前缀);点击
    两行版本信息即复制
    (触屏无悬停,整块即可点区域),expo-clipboard 写入
    • ✓ 反馈;复制失败有错误日志。

用户反馈集成

  • DeviceInfo 新增可选字段 webview(core 类型 + worker 类型同步);
  • 桌面反馈以 Client Hints 完整构建号填充,移动端以探针/桥回传的引擎信息填充;
  • worker 的 issue 正文 Device 区新增 - WebView: ... 一行(旧版本客户端无
    此字段时照常工作)。
  • 注意:worker(packages/feedback-worker)需要重新部署后,issue 里才会出现
    这一行;客户端 payload 侧本 PR 已完整携带。

架构

  • UA 解析下沉到 core(packages/core/src/utils/webview-info.ts),桌面与
    移动共享同一套平台判定,行为不会各自漂移;
  • 两端关于页/反馈的标签统一由 useWebviewLabel()(移动)与
    getWebviewLabel()(桌面)产出;
  • 版本信息拼装抽为 core 的 buildVersionInfo,两端复制格式不会漂移。

代码评审(两轮 OCR,19 + 11 条发现全部处理)

第一轮(19 条)关键修复

  • 移动端复制失败静默(无 try/catch)→ 补 try/catch + 错误日志
  • 复制反馈只在引擎 label 非空时渲染 → webviewLabel || copied 时都渲染
  • Apple 平台版本号缺口:内嵌 WKWebView 的 UA 常无 Version/ 令牌 → 补
    OS x_y 令牌回退
  • 探针无超时/失败兜底(隐藏 WebView 可能常驻会话)→ 5 秒超时 + onError
    自动卸载(开书后桥仍会回填,无损失)
  • 解析器零单测 → 表驱动测试 13 个 UA 样例(Windows/macOS/iOS/Android/
    Linux/通用浏览器/空 UA)
  • 桌面 Client Hints 品牌精确匹配漏配(WebView2 上报独立品牌)→ 放宽为
    /Microsoft Edge/i,与移动端探针一致
  • Android UA 无 ; wv) 标记时误判为 WebKitGTK → Android 判定提前
  • 桌面复制失败只有 console → 补 toast.error
  • 桌面 WebView 行空值 → "..." 占位符
  • 反馈 worker:设备字段直插 issue Markdown(客户端可注入)→ 清洗 +
    长度上限
  • store:回退键值改为引擎缺失(无法识别的 UA 不再让 label 消失)
  • 复制格式两端统一(无 v 前缀)

第二轮(11 条)修复

  • worker sanitizer 漏 @(设备字段可构造 GitHub mention 注入)→ strip
    集合补 @
  • 移动复制反馈在 label 为空时也渲染(webviewLabel || copied
  • 桌面复制失败补 toast.error(与成功路径对称)
  • 桌面 WebView 行 "..." 占位符
  • 两端复制格式统一

顺带的开发环境修复

  • metro.config.js blockList 排除 src-tauri/target:桌面 cargo 构建的
    临时文件会让 Windows 回退监视器崩溃(ENOENT),进而杀死整个 Metro。
  • 设置屏/引导页的语言处理器改为静态导入 changeAndPersistLanguage
    原动态 import() 在 dev 模式触发按需分包请求,失败即静默失效(该模块
    启动时已加载,动态导入毫无收益)。

架构

  • UA 解析下沉到 core(packages/core/src/utils/webview-info.ts),桌面与
    移动共享同一套平台判定,行为不会各自漂移;
  • 两端关于页/反馈的标签统一由 useWebviewLabel()(移动)与
    getWebviewLabel()(桌面)产出。

i18n

7 种语言新增 settings.webviewVersionsettings.copyVersionInfo

验证

  • 桌面:tsc / biome / core 测试 584 通过;release 构建实测 About 显示
    WebView2 152.0.4191.62(完整构建号),复制内容正确。
  • 移动:tsc / biome / vitest 14 通过;安卓模拟器(dev client + Metro)实测
    About 显示引擎与完整版本、点击复制成功;冷启动(未开书)即显示。
  • 两轮 OCR 代码评审(19 + 11 条)全部处理或明确说明。

Reader features vary per engine (@layer/:has floors, execCommand,
clipboard behavior), so bug reports need the engine name and build, not
just the app version. Add a Web-engine row under the app version in
Settings → About, parsed from the UA per Tauri platform:

- Windows: WebView2 (Edg/ token = real Evergreen build)
- Android: system WebView (Chrome/ token, ; wv) marker)
- macOS/iOS: WebKit via the Version/ token (follows the system release,
  unlike readest's frozen AppleWebKit/605.1.15 parse)
- Linux: WebKitGTK without a version — the UA carries only frozen
  tokens; the real one is the system libwebkit2gtk package
- plain vite dev in a browser: generic Chrome/Edge/Firefox/Safari labels

Labels localized for all 7 locales (settings.webviewEngine).
…sion label

The UA string is reduced (Edg/152.0.0.0 on a 152.0.4191.62 WebView2
runtime), so the UA-parsed version showed zeros after the major.
Fetch fullVersionList via User-Agent Client Hints for Chromium-family
engines (WebView2 -> Microsoft Edge, Android WebView -> Android
WebView, Chrome -> Google Chrome) and fall back to the UA value
elsewhere. Rename the About row label to the plainer
settings.webviewVersion in all locales.
Hovering the version card reveals a copy button (readest's About window
has the same affordance — mobile users can't select the version string
for bug reports). Copies both lines at once:

  ReadAny 1.3.5
  WebView2 152.0.4191.62

Feedback via icon swap + toast (common.copied). Uses the same
navigator.clipboard.writeText as the chat/markdown copy buttons.
Hover-reveal hid the affordance; keep it visible like the neighboring
check-update button.
The webview-version feature was desktop-only; bring it to the Expo app's
About screen:

- Move the UA parser to core (packages/core/src/utils/webview-info.ts)
  so both apps produce identical engine/version labels; the desktop
  wrapper keeps its Tauri check and Client Hints lookup.
- The mobile app has no real UA (App.tsx polyfills "ReactNative"), so
  reader.html now reports its own navigator.userAgent over the RN bridge
  once at startup (type: readany-ua); use-reader-bridge stores it in a
  small zustand store. It is the system WebView's actual UA — the exact
  engine the book content renders in.
- AboutScreen gains a tappable WebView-version row: touch has no hover,
  so the row itself is the affordance — tapping copies both versions
  (ReadAny vX + engine label) via expo-clipboard, with a transient
  check-mark feedback. Before a book is opened it shows the engine name
  without a build.
readest's mobile app can show the engine at launch because its entire
UI runs inside the system WebView; our native UI has no webview until a
book is opened, so the About screen previously fell back to an
engine-only label. Add a 0x0 hidden probe WebView that posts its
navigator.userAgent to the webview-info store on first launch and then
unmounts — the About screen shows the full engine/build immediately,
and the reader bridge keeps it fresh afterwards.
Android system WebViews reduce their UA too (Chrome/138.0.0.0), so the
probe now also asks Client Hints for fullVersionList and stores the real
build alongside the UA; the reader bridge keeps it when its messages
carry no version. About label prefers the full build.
Drop the separate labeled section and its copy hint — show the web
engine build as a second line right under v1.3.6, and make the
two-line block itself tappable to copy both. Matches the desktop
About card's behavior with a flatter mobile layout.
The installed expo is 54.0.33 (SDK 54); the About screen hardcoded
"Expo SDK 55" and drifted. Derive the label from the expo package's
major version (matches the SDK number since SDK 51) so it follows
future upgrades, and fix the platform-service comment.
Bug reports need the engine axis (justify fallbacks, clipboard, and CSS
capability floors all vary per webview build). Add an optional
DeviceInfo.webview to the shared type, fill it on both apps:

- desktop: full build via Client Hints (getWebviewLabel)
- mobile: engine + build from the reader-UA store (probe/bridge)

and render it as a WebView line in the worker's GitHub issue body
(packages/feedback-worker), falling back to "unknown" for old
clients. The field is optional, so payloads from versions without this
change keep submitting fine.
OCR code review follow-ups (all severities):

- core parser: Android UA without the wv marker no longer falls through
  to WebKitGTK; Apple platforms fall back to the OS x_y token when the
  Version/ token is absent (embedded WKWebView UAs often omit it)
- UAProbe: unmount after a 5s timeout or WebView error instead of
  staying mounted all session; guard the injected postMessage; brand
  list cross-referenced with the desktop client-hints lookup
- AboutScreen/AboutSettings: copy feedback timer cleared on unmount;
  copied confirmation renders even when the engine label is empty;
  copy payload format unified across platforms
- feedback worker: device fields sanitized before issue-body render
- webview-info parser: table-driven unit tests (13 cases) added
Device fields are client-supplied and rendered verbatim into issue
Markdown — strip line breaks and Markdown-significant characters
(backticks, brackets, angle brackets, bangs) and cap length so they
cannot inject headings, links, or mentions.
Metro's Windows fallback watcher fatally crashed (ENOENT) on transient
files under packages/app/src-tauri/target while the desktop tauri
dev/build ran, killing the mobile bundler. Exclude src-tauri/target
from the Metro file map via blockList.
The settings/onboarding language handlers dynamically imported
@readany/core/i18n; in dev Metro serves that as an on-demand module
bundle which failed to load on this Windows setup, so the language
switch silently died in the catch. The module is already in the
startup bundle (App.tsx imports it), so import it statically.
Medium:
- feedback worker: sanitize strips @ so device fields cannot inject
  GitHub mentions; also flattens line breaks and caps length
- UAProbe: unmounts after a 5s timeout or WebView error instead of
  staying mounted all session when the injected script never reports
- AboutScreen: copied confirmation renders even when the engine label
  is empty (tap is always acknowledged)
- core parser: Apple platforms fall back to the OS x_y token when the
  Version/ token is absent (embedded WKWebView UAs often omit it)
- build-reader entry: reports the Client Hints full build with the UA
  (the reader page is a secure context, unlike the probe's inline HTML)

Low:
- store: fallback keyed on a missing engine; ?? -> || so an empty
  fullVersion falls back to the UA parse; fallback lookup map instead
  of nested ternaries
- lib/webview-info: brand match loosened to /Microsoft Edge/i (WebView2
  reports a distinct brand); reuses core formatWebviewInfo; drops the
  dead || version
- AboutSettings: type=button on the copy/update buttons; '...' webview
  placeholder; failure surfaces via toast.error; timer cleanup on
  unmount
- AboutScreen: copy payload format unified with desktop (no v prefix);
  timer cleanup on unmount
- core parser: table-driven unit tests (13 UA samples)
@k6G52m4Dz75W k6G52m4Dz75W changed the title feat: 关于页展示 Web 引擎与 WebView 完整版本号(桌面 + 移动端) feat(about): 关于页展示 Web 引擎与 WebView 完整版本号(桌面 + 移动端) Sep 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant