Skip to content

TB-300: 人工筛选并移植 fork 截止 1dd4b0a 的非 Preview 改动 - #140

Merged
hehetoshang merged 7 commits into
mainfrom
promote/stable-fork-through-1dd4b0a
Oct 4, 2026
Merged

hehetoshang merged 7 commits into
mainfrom
promote/stable-fork-through-1dd4b0a

Conversation

@hehetoshang

Copy link
Copy Markdown
Collaborator

将 hehetoshang/moke 截止 1dd4b0aa908a4e935fc10dc434c1631519de4a64(包含该提交)的非 Preview 功能移植到稳定版。人工检查 37 个源提交后,在 talebook/moke 最新 main 基线 484475e 上逐项 cherry-pick 或提取。完整处置理由见 逐提交审计。

变更与来源

  • 1dd4b0a:默认不导入的原创示例 EPUB、开发者导入/删除入口、离线书架与本地进度隔离;适配新版 README,保留 upstream 漫画阅读器逻辑。
  • ba5a9fe:手动提取稳定桌面的最小窗口尺寸取消及回归测试,排除 Preview 配置和运行时。
  • 5a33e4b:手动提取扩展 REST 并发/正文上限、WS 握手并发/超时/消息上限及 Rust 测试。与已有 fix(security): harden extension API and WebSocket boundary #115 的资源限制有重叠,后续合并需去重;本 PR 不替代其完整 Host/Origin/CORS/token 加固。
  • 979eddb:OHOS 华为 Profile 包名修复、单一包名的 CLI 校验例外、显式 OHOS 配置与 HAP 身份断言。未移入签名发布流程。
  • 8490772:手动提取 iOS 名称校验对显式构建配置的支持;测试改为普通 Custom 配置,排除 Preview 身份校验。
  • 375109b / 5a33e4b:手动提取 updater 清单通用校验、参数化及确定性平台选择;稳定 tag、GitHub 下载地址、Darwin 更新及无签名时成功跳过均保留。

明确排除 Preview 授权、激活门禁、设备租约/换机、控制平面、受控更新授权、设备凭据、签名分发、Preview 配置/版本策略及 fork 的 CI 豁免。没有整体合并 fork,没有修改版本号或 Reader/vendor gitlink。

验证

  • pnpm test:481/481 通过。
  • pnpm lint、pnpm typecheck:通过。
  • Playwright MCP:桌面 1280×900、移动 390×844/900;导入、刷新后持久化、删除、503 错误重试、键盘 Enter、本地书架加入/移出、离线书库展示均验证。示例操作未请求 Talebook book API;控制台仅有既存 favicon 404 与主动注入的 sample 503。
  • 新增 tests/sample-book.spec.mjs:移动持久化/删除、加载禁用/错误恢复通过;桌面首次导航超时,单独复验通过。此机器使用 /usr/bin/chromium 和放宽超时的临时配置;标准启动首轮有 Chromium 创建页面超时。
  • node --test tests/updater-manifest.test.mjs:4/4 通过(包含在 481 项内)。
  • rustfmt --check --edition 2021(两个受改动 Rust 模块):通过。实际 vendored Tauri CLI 的 OHOS patch 可应用且幂等。
  • cargo test --lib --locked:首轮缺 vendor 依赖,初始化固定 gitlink 后重试进入编译;因本机空间不足风险(剩余约 3 GB,且共享机器有其他编译)停止本次编译,Rust 测试未完成。

待验证与质量门禁

OHOS identifier 从 org.houheya.moke 改为 org.houheya.moke_openharmony,旧包直接升级、数据保留及与 Preview 共存尚未做设备验证。桌面/移动原生 Reader 打开、返回、文件清理,完整原生构建及 Rust 测试未验证。

Draft:等待 Mika 协调 Sol → Grok 在固定 head SHA 首轮评审、统一清单及双模型复核,当前没有两位 QA 的通过结论。此 PR 仅提交可审查实现,不自动批准、合并、发布或部署。

Related TB-300

* fix(ohos): align bundle identifier with Harmony debug profile

* fix(ohos): accept Huawei debug profile identifier in CLI

(cherry picked from commit 979eddb)
Bundle one 1933-byte original EPUB without images or fonts. Import and delete it explicitly from developer settings using existing offline storage and reader flows. Keep sample shelf actions and reading progress local; cover persistence, retries, concurrent import/deletion, and native cleanup.

(cherry picked from commit 1dd4b0a)
Manually extract stable configurations and regression coverage from hehetoshang/moke ba5a9fe. Exclude Preview configuration and runtime.
Extract stable transport resource limits and Rust unit tests from hehetoshang/moke 5a33e4b. Exclude Preview entitlement, lifecycle and credential changes. This overlaps the resource limits in upstream PR #115.
Extract generic app-name configuration support from hehetoshang/moke 8490772. Adapt the test to an ordinary custom product name; exclude Preview identity and release changes.
Extract generic manifest generation from hehetoshang/moke 375109b, 5a33e4b and their final state at 1dd4b0a. Keep stable GitHub download defaults, Darwin updates and successful no-signature skip. Exclude Preview tag handling, Darwin suppression, authorization and release workflows.
Comment thread src/app/shelf/page.tsx
}, [selectedIds, batchMode]);

// ── Single-item actions (from right-click / long-press menu) ────────────
const updateShelf = async (id: string) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

新增的 state.wants 在离线书架里会被丢弃。

同文件 src/app/shelf/page.tsx:208-211 是:

buildOfflineLibrary(records.filter((record) => record.inShelf === true)).map((record) => ({
  ...record,
  state: { download: 1 },   // ← 整体覆盖,wants 没了
}))

buildOfflineLibrary 在本 PR 里新增了 state: { wants }(src/lib/offline-library.ts:10,19,34),但这里用 { download: 1 } 把整个 state 覆盖掉,结果 BookTable 的「在书架」徽标(BookTable.tsx:282)和 wants 排序(BookTable.tsx:96)在离线书架中永远为 falsy。离线书架的 canRemoveShelf 菜单项本身是写死的 移出书架,所以功能不受影响,但状态显示不一致,且本 PR 新增的字段在三个消费方里只有一个半生效(library/page 和 search/page 生效,shelf/page 不生效)。

建议改成保留已有 state:

state: { ...record.state, download: 1 },

Comment thread src/lib/offline-books.ts
}
return records
.filter((record) => !serverUrl || sameServer(record.serverUrl, serverUrl))
.filter((record) => !serverUrl || sameServer(record.serverUrl, serverUrl) || isSampleBook(record))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

|| isSampleBook(record) 让示例书对任意 serverUrl 都出现在 listOfflineBooks() 的结果里。除了预期的离线书架 / 离线书库 / 离线搜索,它还会流到:

  • src/app/downloads/page.tsx:89 → 「下载管理」在每个服务器下都会列出内置示例书;
  • src/app/detail/page.tsx:198 → 本地记录按 bookId 匹配(示例书 ID 是固定字符串,不会误命中 Talebook 数字 ID,风险低)。

我确认过写入路径都用 sameServer 收窄:getOfflineBook、setOfflineBookShelfState、deleteOfflineBook、syncOfflineDownloadState 都不会因为跨服务器可见而误改/误删别的服务器数据(新测试也覆盖了这点),所以这里不是数据隔离漏洞。只是请确认「下载管理里每个服务器都显示示例书」是预期产品行为;如果不是,筛选条件可能需要再收一层(例如只在 offline mode 或首次导入时返回)。

}

export async function fetchReadingProgress(bookId: string | number, signal?: AbortSignal): Promise<ReadingProgressPayload | null> {
if (bookId === SAMPLE_BOOK.bookId) return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

示例书的阅读进度在 Moke 这一侧被完全丢弃:fetchReadingProgress 返回 null,saveReadingProgress(第 92 行)直接 return。这样确实不会把内置书写进 Talebook 的 /readstate,但 Moke 也不再有示例书的任何进度来源。

ReaderProgressProvider.tsx 是唯一的上报入口(事件 → saveReadingProgress),所以请确认 readest 自身会按 mokeBookId 落盘、重新打开能续读;否则示例书每次都会从头开始,而 openOfflineBook 里 restoreProgress 恒为 null 会让这个现象很难排查。

另外这里对 bookId: string | number 直接用 === 比较字符串常量,只有内置 ID 会命中,建议补一句注释说明「仅内置示例书,避免与服务器书籍 ID 混淆」,与 sample-book-info.ts 里的约定保持一致。

.to_string(),
)
.with_status_code(503);
let _ = request.respond(response);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里有两个小问题:

  1. 缺少 CORS 头:正常错误路径(respond_api_error)以及 401 / health 响应都会带 cors_header,只有这个新增的 503 没带。对浏览器扩展来说,跨源请求会变成不透明的 CORS 失败,而不是能读到 SERVER_BUSY 正文的 503。建议直接复用 respond_api_error(request, ApiError{status:503, code:"SERVER_BUSY", ...}, cors_header)。
  2. 在 accept 线程里同步回写:正常请求路径是 thread::spawn(move || handle_request(...)),而这个 503 是直接在 accept 循环里调用 request.respond() 写 socket。响应体很小、通常能进内核发送缓冲,但如果对端不读,写仍可能阻塞,从而卡住整个 accept 循环(正好是这个 PR 想避免的那类问题)。建议放进独立线程,或与 32 个 slot 一样由有界线程池处理。

if !matches!(method, tiny_http::Method::Post | tiny_http::Method::Put) {
return Ok(None);
}
let declared = declared

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

两层限流本身没问题(先看 Content-Length、读取时再 take(MAX+1) 兜底,容量按 declared.min(MAX) 预分配也对)。但这里引入了一个行为变化需要确认:

  • 强制要求 Content-Length:用 chunked transfer-encoding 上传的 POST/PUT(fetch 传 ReadableStream、部分 HTTP 客户端/代理默认分块)会直接拿到 411。如果扩展协议里存在流式请求体,这是破坏性变更;如果不存在,建议在扩展 API 文档里写明「必须先给出 Content-Length」。
  • 非 POST/PUT 不读正文:validate_declared_body_length 对其他方法返回 Ok(None),正文既不被读取也不被消费。当前路由里 DELETE 不用 body,所以没有实际问题,但 DELETE 带 body 会被静默忽略——这点值得在文档或注释里显式说明,否则以后加路由时很容易踩。

"$schema": "https://schema.tauri.app/config/2",
"productName": "Moke",
"identifier": "org.houheya.moke",
"identifier": "org.houheya.moke_openharmony",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

应用身份变更需要谨慎对待:

  • houheya 是 fork 作者的个人命名空间,稳定版长期沿用这个反域名建议再确认一次(是否应该用 talebook 自己的命名空间)。审计文档也把这点列为待 QA 结论。
  • 这是应用身份变化,不只是显示名:旧稳定包(org.houheya.moke)→ 新包(org.houheya.moke_openharmony)的直接升级、数据保留、以及与 Preview 共存都还没做设备验证(PR 描述已列为待验证项)。在拿到结论前不建议进入发布。
  • 该字符串在 .github/workflows/build-release.yml:548 的断言里又硬编码了一遍,两处必须同步修改,否则发布会在断言步骤失败。

另外确认一下:为这一个标识符在 Tauri CLI 里开下划线例外,是否会和华为 Profile 校验或其他平台(Android/iOS)的标识符规则产生冲突。

.any(|ch| !(ch.is_alphanumeric() || ch == '-' || ch == '.'))
{`;
const replacement = ` // Huawei OpenHarmony debug profiles may use this app-specific underscore.
if config.identifier != "org.houheya.moke_openharmony"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 patch 把标识符硬编码进脚本,和 src-tauri/tauri.ohos.conf.json 形成隐式耦合:

  • 一旦配置里的包名改了(比如去掉 houheya),CLI 的例外不会生效,只会在构建时报「identifier 非法」——属于 fail-closed,但排查成本高。建议从 tauri.ohos.conf.json 读取标识符,或至少在脚本里显式断言配置值等于这个常量。
  • 脚本会就地修改 vendor/tauri 子模块(writeFileSync(sourcePath, ...)),在 CI 里会让子模块工作区变脏。目前后续步骤看起来不依赖干净子模块,但若以后有 git submodule update / 清洁性检查步骤就会冲突,建议在注释里写明这一点(幂等性本身由测试覆盖了,这点没问题)。

@@ -0,0 +1,65 @@
import { test, expect } from '@playwright/test';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个 Playwright spec 目前不会在 CI 里执行:

  • .github/workflows/ci.yml:167 只显式传了 tests/responsive-smoke.spec.mjs tests/captcha-sandbox.spec.mjs;
  • 仓库里没有 playwright.config.*,所以没有 testDir 之类的默认发现规则;
  • 根 pnpm test 只匹配 tests/*.test.mjs,也覆盖不到 .spec.mjs。

PR 描述把示例书的 UI 验证主要归在这个文件(导入/刷新持久化/删除/503 重试/键盘 Enter)。既然它不是 dead code 就是 lint 目标之外的文件,建议把它加进 ci.yml 的 playwright 命令行(并确认 CI 环境里有它依赖的 MOKE_E2E_BASE_URL 与已导入的 privacy-consent / developer-storage 初始状态)。

'src-tauri/tauri.windows.conf.json',
];

test('desktop windows remain resizable without a minimum size', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

两个点:

  1. 测试把四份桌面配置的路径写死(第 5-10 行)。将来新增桌面/平板配置(仓库里已有 tauri.mobile.conf.json、tauri.tablet.conf.json)不会自动纳入校验,反而会让「没有 minWidth」这个约束悄悄失效。可以考虑用 glob 扫描 src-tauri/tauri*.conf.json,或至少把这层意图写进测试名/注释。
  2. 更想确认产品侧的取舍:完全移除 minWidth/minHeight 后桌面窗口可以缩到很小,主界面会不可用(原来的 800×600 是有意的最小值)。如果目的只是为小屏/墨水屏设备放宽限制,建议保留一个更小的下限(例如 480×360),而不是彻底去掉;如果确实要允许任意尺寸,请在 PR 里说明原因。

@hehetoshang
hehetoshang marked this pull request as ready for review October 4, 2026 18:14
@hehetoshang
hehetoshang merged commit 25cc8a1 into main Oct 4, 2026
38 checks passed
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