mirror of
https://github.com/sinanyuntu/trade-message-center.git
synced 2026-09-17 13:22:11 +08:00
fix: align harness permissions and development-only usage
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
# 服务端前端组件规范
|
||||
|
||||
## 当前状态
|
||||
|
||||
服务端包没有 UI 框架和组件文件。当前不能从仓库推导出组件目录、Props 形式、组合方式或样式实现。
|
||||
|
||||
## 当前可执行的边界
|
||||
|
||||
- 不要在没有渲染入口和前端依赖时创建框架组件。
|
||||
- 组件公开输入应使用明确的 TypeScript 类型;服务端 API 响应不能未经适配直接成为 UI 的长期 Props 契约。
|
||||
- 组件不负责建立数据库连接、调用任意服务端内部模块或拼装未定义的 API 错误结构。
|
||||
- 样式方案和可访问性测试方式在首个 UI 任务中确定后,必须用真实示例更新本文件。
|
||||
|
||||
## 首次组件落地要求
|
||||
|
||||
需说明组件的渲染环境、目录归属、Props/事件契约、加载/空/错误状态、样式隔离方式、键盘与焦点行为,以及可执行的验证命令。
|
||||
|
||||
## 可访问性最低要求
|
||||
|
||||
面向用户的 UI 必须提供可访问名称、正确的语义元素和可键盘完成的核心操作。服务端渲染场景还要验证输出 HTML 与客户端交互之间的边界。
|
||||
|
||||
## 常见误区
|
||||
|
||||
- 把 Fastify 路由处理器直接当成组件数据层,跳过稳定的 API 契约。
|
||||
- 在组件中复制服务端 DTO 的字段解析,导致后端变更时多处失配。
|
||||
- 在没有真实复用需求时提前建立通用设计系统。
|
||||
@@ -1,38 +0,0 @@
|
||||
# 服务端前端目录结构
|
||||
|
||||
## 当前状态
|
||||
|
||||
服务端包不包含原生 HTML 联调页,也没有前端构建产物:
|
||||
|
||||
```text
|
||||
apps/
|
||||
├── server/
|
||||
│ └── src/ # Fastify HTTP/WS 和业务实现
|
||||
└── mind-test-harness/
|
||||
└── src/ # 独立联调页与 Mind 外部依赖模拟
|
||||
```
|
||||
|
||||
`mind-test-harness` 自己监听页面端口,并通过显式 Bright base URL 读取会话、历史、插件状态和同步事件;不连接数据库、不读取 Mind legacy 消息、不引入 UI 框架或前端依赖。server 不注册 `/harness` 路由,也不导入该包。
|
||||
|
||||
## 边界规则
|
||||
|
||||
- 服务端 API、业务逻辑和数据访问不属于本前端层,即使它们最终为 UI 提供数据,也应留在 backend 规范描述的边界内。
|
||||
- 如果未来增加正式管理界面或服务端渲染,入口、静态资源、组件和页面目录必须基于实际构建工具重新确定,并在这里登记;不要把联调页直接演化为正式应用。
|
||||
- 不要把扩展包的页面或资源复制到服务端包;两个包通过明确的 API/消息契约协作。
|
||||
|
||||
## 联调页边界
|
||||
|
||||
- 页面脚本只负责输入、请求编排、运行时响应校验、状态展示、历史分页和事实去重;HTTP/WS route、service、repository 保持在 server backend 层。
|
||||
- 页面使用独立 history cursor 恢复;重连时先重新读取历史,再建立 WS,避免把内存中的事件状态当作可靠存储。
|
||||
- 页面展示 raw `conversationId`/`messageId`/sender/login/direction/sentAtMs,文本进入 `innerHTML` 前必须转义。
|
||||
- 发送按钮在当前未实现发送链路时保持禁用,不得伪造成功或写入客户端 outbox。
|
||||
|
||||
## 命名
|
||||
|
||||
包目录为 `server`、包名为 `@trade-message-center/server`。`mind-test-harness` 仅用于开发手工测试,不能被 server 或其它包依赖。若加入正式 UI,再按其真实入口和功能边界拆分文件。
|
||||
|
||||
## 参考文件
|
||||
|
||||
- [`apps/server/package.json`](../../../../apps/server/package.json):当前服务端包的真实内容。
|
||||
- [`apps/mind-test-harness/package.json`](../../../../apps/mind-test-harness/package.json):独立开发测试工具,不参与构建。
|
||||
- [`tsconfig.base.json`](../../../../tsconfig.base.json):共享编译配置。
|
||||
@@ -1,22 +0,0 @@
|
||||
# 服务端前端 Hook 规范
|
||||
|
||||
## 当前状态
|
||||
|
||||
服务端包没有 UI 框架、Hook 文件或数据请求库。当前没有可以复用的 Hook 模式,也没有服务端前端的请求缓存契约。
|
||||
|
||||
## 当前边界
|
||||
|
||||
- 只有实际引入支持 Hook 的前端技术后,才使用 `use...` 命名;普通数据转换函数不应伪装成 Hook。
|
||||
- Hook 只组合 UI 状态和生命周期;HTTP 请求、鉴权、错误转换等边界逻辑应由明确的客户端适配模块承载。
|
||||
- 不要在多个 Hook 中各自解释同一 API 响应,统一类型和边界校验后再交给 UI 使用。
|
||||
- 当前没有 React Query、SWR 或其他缓存库,不要凭空规定缓存刷新策略。
|
||||
|
||||
## 首次建立约定时
|
||||
|
||||
提供至少两个真实使用示例,并写明依赖变化、异步状态、取消/卸载、错误显示和测试方式。若 UI 运行在服务端渲染环境,还需说明服务端与客户端执行边界。
|
||||
|
||||
## 常见误区
|
||||
|
||||
- 把服务端内部函数直接导入 Hook,绕过 HTTP 或消息契约。
|
||||
- 在 Hook 中吞掉请求错误,只返回空列表,让界面无法区分失败和无数据。
|
||||
- 为了共享一个无状态格式化函数而引入 Hook 依赖。
|
||||
@@ -1,32 +0,0 @@
|
||||
# 服务端前端开发规范
|
||||
|
||||
> `@trade-message-center/server` 是纯服务端包,不包含本地 HTML harness、正式前端框架或生产 UI。
|
||||
|
||||
## 当前基线
|
||||
|
||||
- `apps/server/package.json` 只有 Fastify 依赖,没有 React/Vue/Svelte、样式库或独立前端构建工具。
|
||||
- 手工 Bright 联调页面归 `apps/mind-test-harness/` 所有;server 只提供其 HTTP/WS 业务边界。
|
||||
- TypeScript 仍必须遵循根 `tsconfig.base.json` 的严格模式和 `NodeNext` 模块解析。
|
||||
|
||||
## 规范目录
|
||||
|
||||
| 文档 | 内容 | 状态 |
|
||||
| --- | --- | --- |
|
||||
| [项目级代码架构](../../project/architecture.md) | 所有包共用的文件职责、共享层级、main-last 与注释规则 | 项目级必读 |
|
||||
| [目录结构](./directory-structure.md) | 原生联调页与未来正式 UI 边界 | 已建立 |
|
||||
| [组件规范](./component-guidelines.md) | 组件引入前的最小边界 | 已建立基线 |
|
||||
| [Hook 规范](./hook-guidelines.md) | Hook 与服务端数据请求边界 | 已建立基线 |
|
||||
| [状态管理](./state-management.md) | UI 状态与服务端状态分类 | 已建立基线 |
|
||||
| [质量规范](./quality-guidelines.md) | 原生联调页的质量和验证方式 | 已建立 |
|
||||
| [类型安全](./type-safety.md) | TypeScript 和运行时边界 | 已建立基线 |
|
||||
|
||||
## 开始前检查
|
||||
|
||||
1. 先阅读 [项目级代码架构](../../project/architecture.md),再确认这次工作确实在服务端包内引入了 UI 或服务端渲染,而不是把 API/路由误归入本层。
|
||||
2. 读取服务端 backend 规范,明确 UI 与 HTTP/业务层的接口边界。
|
||||
3. 首次引入前端依赖时,同时记录入口、构建脚本、渲染环境和验证命令。
|
||||
4. 运行根 `pnpm typecheck`、`pnpm build`、`pnpm test`,并区分真实执行与因 `--if-present` 跳过的脚本。
|
||||
|
||||
## 文档维护
|
||||
|
||||
当服务端出现正式前端代码后,用真实组件、入口和测试补充本文件;不要把 `mind-test-harness` 的手工测试能力重新放回 server。
|
||||
@@ -1,45 +0,0 @@
|
||||
# 服务端前端质量规范
|
||||
|
||||
## 已确认的工具链
|
||||
|
||||
- 使用 pnpm `11.7.0`、Node.js `>=22.22.2 <23` 和根级严格 TypeScript 配置。
|
||||
- 根级 `dev`、`build`、`typecheck`、`test` 命令只调用实际存在的子包脚本;手工联调页由 `mind-test-harness` 独立提供,server 不新增前端构建脚本。
|
||||
- 根级 `format` 和 `format:check` 使用 Oxfmt;lint-staged 只格式化暂存文件,Husky 在 `pre-commit` 阶段触发该检查。
|
||||
- 当前没有 lint、组件测试、端到端测试或浏览器构建工具,不能把这些能力写成已经启用的检查项。
|
||||
|
||||
## 必须遵守
|
||||
|
||||
- UI 代码必须通过根 `tsconfig.base.json` 的严格检查,并保持与服务端 API/消息契约的类型边界。
|
||||
- 用户可见状态至少要考虑加载、空数据、错误和权限失败;实际测试方案确定后补上自动化验证。
|
||||
- 联调页必须显式展示加载、空数据、插件 online/offline、授权失败、历史读取失败、WS 断线和同步状态;发送能力未接入时保持禁用。
|
||||
- 联调页只能调用 Bright HTTP/WS;不得在浏览器脚本中访问数据库、server 内部模块、Mind legacy 接口或保存 credential。
|
||||
- 来自 HTTP/WS 的未知数据必须先运行时校验;消息正文进入 DOM 前必须转义,事实去重键必须包含 scope、会话和原始 messageId。
|
||||
- 联调页专属依赖和脚本写入 `apps/mind-test-harness/package.json`;该包不应声明 build/test 脚本,也不得成为其它包依赖。
|
||||
- 引入浏览器端能力时,明确哪些代码只能在客户端执行,避免在服务端环境访问浏览器全局对象。
|
||||
|
||||
## 禁止做法
|
||||
|
||||
- 未经任务说明把服务端包改造成前端应用,或直接复制扩展包实现。
|
||||
- 用 `any`、无理由断言或静默默认值掩盖 API 契约问题。
|
||||
- 在组件中直接访问数据库、环境密钥或服务端内部模块。
|
||||
- 宣称没有配置的 lint/测试工具已经通过。
|
||||
|
||||
## 验证命令
|
||||
|
||||
```bash
|
||||
pnpm format:check
|
||||
pnpm typecheck
|
||||
pnpm build
|
||||
pnpm test
|
||||
```
|
||||
|
||||
首次出现服务端前端后,应把实际入口和测试脚本接入 workspace,并记录浏览器/SSR 场景的额外验证方法。
|
||||
|
||||
当前联调页没有独立浏览器构建或 E2E runner;最低验证包括服务端 typecheck/build/test、`GET http://127.0.0.1:8788/` 内容 smoke,以及真实本地 HTTP/WS 网络 smoke。
|
||||
|
||||
## 评审清单
|
||||
|
||||
- 是否保持浏览器、服务端和 API 边界清晰?
|
||||
- 是否如实区分了自动化检查与手工验证?
|
||||
- 是否覆盖了加载、空、错误、权限、插件离线、重连恢复和可访问性状态?
|
||||
- 是否引入了未经记录的 UI 框架、状态库或构建工具?
|
||||
@@ -1,22 +0,0 @@
|
||||
# 服务端前端状态管理
|
||||
|
||||
## 当前状态
|
||||
|
||||
当前没有服务端前端入口、状态文件、状态库或数据缓存层。不能把扩展包的未来状态方案直接复制到这里。
|
||||
|
||||
## 现阶段规则
|
||||
|
||||
- 不要在没有第二个状态消费者前引入全局状态库。
|
||||
- 新增状态前先区分视图局部状态、跨页面状态、服务端数据和派生值。
|
||||
- API 返回数据的缓存、失效和重新获取规则必须在请求边界确定,不能让每个组件自行决定。
|
||||
- 可从其他状态计算出的值不应重复存储,除非有明确的性能或快照需求。
|
||||
|
||||
## 首次落地时的记录项
|
||||
|
||||
补充状态库或持久化方案时,需要说明初始化和清理时机、服务端渲染/客户端 hydration 边界、错误与加载状态、缓存失效方式和测试入口,并引用真实使用文件。
|
||||
|
||||
## 常见误区
|
||||
|
||||
- 把服务端数据库状态、请求缓存和 UI 交互状态混在一个 store 中。
|
||||
- 将 API 响应复制到多个局部状态,导致刷新后出现不一致。
|
||||
- 没有定义权限或会话变化时的清理策略。
|
||||
@@ -1,28 +0,0 @@
|
||||
# 服务端前端类型安全
|
||||
|
||||
## 编译基线
|
||||
|
||||
服务端前端代码应使用根 [`tsconfig.base.json`](../../../../tsconfig.base.json),当前已确认 `strict: true`、`target: ES2022`、`module: NodeNext`、`moduleResolution: NodeNext`、大小写一致性检查和 JSON 模块导入。
|
||||
|
||||
## 类型组织
|
||||
|
||||
当前没有服务端前端源文件或共享类型目录。类型先靠近实际使用的页面/组件或 API 适配模块;当多个功能共享同一 API、消息或表单契约时,再抽到包内明确的共享位置。不要通过相对路径引用扩展包内部类型。
|
||||
|
||||
## 运行时校验
|
||||
|
||||
服务端包当前没有运行时 schema 库。来自 HTTP、数据库、扩展消息或服务端渲染输入的数据不能只依赖编译期类型;Mind 联调页(`apps/mind-test-harness`)的原生浏览器脚本使用显式 shape guard 校验 scope、conversation、history、message、plugin status 和 sync status,失败时进入可见错误状态。
|
||||
|
||||
## 常见模式
|
||||
|
||||
- API 响应类型和 UI 展示模型可以不同,转换应集中在适配边界而不是散落在组件中。
|
||||
- 对可选字段、空列表、错误响应和权限失败使用显式联合类型或状态类型,避免用空字符串/空对象代表所有失败。
|
||||
- 复用类型前先搜索已有定义,避免多个消费者各自断言同一原始 payload。
|
||||
- 原生页面的动态文本必须经过 HTML 转义;不要把未经校验的 API 字段直接拼入 `innerHTML`。
|
||||
- 历史 cursor 由服务端生成和验证,浏览器只保存/回传 opaque string,不解析或重建 cursor 内部字段。
|
||||
|
||||
## 禁止做法
|
||||
|
||||
- 用 `any` 绕过 API 或表单类型错误。
|
||||
- 对服务端响应直接使用 `as`,没有运行时检查或可信来源说明。
|
||||
- 在 UI 层重复复制后端 DTO 字段,并让两份定义长期漂移。
|
||||
- 为了方便导入而破坏 workspace 包边界。
|
||||
@@ -110,7 +110,7 @@ DATABASE_URL=postgres://postgres:postgres@127.0.0.1:5432/trade_message_center
|
||||
|
||||
### OneTalk 本地开发
|
||||
|
||||
`pnpm dev` 只以 `NODE_ENV=development` 启动 Bright server 与 Chrome 扩展。`mind-test-harness` 是顺道使用的低优先级手工工具,必须另开终端运行 `pnpm dev:harness`;它的失败只报告给该命令,绝不阻断主开发流程。启动后,其 Mind 授权模拟默认监听 `127.0.0.1:8787`,独立 Bright 联调页默认监听 `127.0.0.1:8788`。Bright 通过 `MIND_AUTH_BASE_URL` 访问模拟的两个授权接口,不再需要 `ONETALK_DEV_*` 身份 fixture;该工具不连接 Mind 数据库,也不属于生产授权实现。
|
||||
`pnpm dev` 只以 `NODE_ENV=development` 启动 Bright server 与 Chrome 扩展。`mind-test-harness` 仅允许在 `NODE_ENV=development` 下运行,是顺道使用的低优先级手工工具;必须另开终端执行 `NODE_ENV=development pnpm dev:harness`,不得在 test、staging 或 production 环境启动。它的失败只报告给该命令,绝不阻断主开发流程。启动后,其 Mind 授权模拟默认监听 `127.0.0.1:8787`,独立 Bright 联调页默认监听 `127.0.0.1:8788`。Bright 通过 `MIND_AUTH_BASE_URL` 访问模拟的两个授权接口,不再需要 `ONETALK_DEV_*` 身份 fixture;该工具不连接 Mind 数据库,也不属于生产授权实现。
|
||||
|
||||
联调页的“上传本地文件到 OSS”把原始文件 POST 给 Bright,再由 server 使用本地环境中的 `OSS_*` 凭据 PUT 到 `harness-uploads/<uuid>/<fileName>`;浏览器不会接触 AccessKey,也不依赖 OSS 浏览器 CORS。成功后页面展示 30 分钟有效的 HTTPS 地址,并自动填入图片或附件发送字段。运行时必须让 `MIND_PAGE_ORIGIN` 与联调页 Origin 精确一致;若四项 `OSS_*` 都未配置,接口返回 `503 oss_upload_unavailable`,不会伪造地址。
|
||||
|
||||
@@ -136,7 +136,7 @@ Bright 不连接 Mind 数据库、不读取认证视图、不共享 Mind 登录
|
||||
|
||||
`mind-test-harness` 是仅供手工测试的独立开发工具:它模拟两个 Mind 授权接口,并提供 Bright HTTP/WS 联调页。Mind 模拟默认监听 `127.0.0.1:8787`,默认 fixture 是账号 `286995452`、binding `binding-123` 和 Cookie `mind_session=mock-valid`;通过 `MIND_MOCK_*` 环境变量覆盖 fixture 后重启即可模拟 binding/version/cookie 切换。两个 endpoint 还可独立设置结果:`MIND_MOCK_BINDING_RESULT=allow|scope_mismatch|binding_revoked|authorization_unavailable`,`MIND_MOCK_SESSION_RESULT=allow|auth_required|scope_mismatch|authorization_rejected|authorization_unavailable`。
|
||||
|
||||
根目录执行 `pnpm dev` 时不会启动支架;需要手工验证时才另开终端运行 `pnpm dev:harness`。支架不被任何 workspace 包依赖,不含测试文件,不声明 build 脚本,也不进入根 `dev`、`typecheck`、`test`、`build` 或生产 Docker 镜像。支架错误只在其独立命令中报告,不能改变主流程结果。
|
||||
根目录执行 `pnpm dev` 时不会启动支架;需要手工验证时才另开终端在开发环境运行 `NODE_ENV=development pnpm dev:harness`。支架不被任何 workspace 包依赖,不含测试文件,不声明 build 脚本,也不进入根 `dev`、`typecheck`、`test`、`build` 或生产 Docker 镜像。支架错误只在其独立命令中报告,不能改变主流程结果。
|
||||
|
||||
直接验证两个接口:
|
||||
|
||||
@@ -150,15 +150,15 @@ curl -sS http://127.0.0.1:8787/internal/bright/onetalk/authorize-session \
|
||||
--data '{"channelAccountId":"286995452"}'
|
||||
```
|
||||
|
||||
手工验证时单独运行:
|
||||
手工验证时只允许在开发环境单独运行:
|
||||
|
||||
```bash
|
||||
pnpm dev:harness
|
||||
NODE_ENV=development pnpm dev:harness
|
||||
```
|
||||
|
||||
`pnpm --filter @trade-message-center/server dev:mind-http` 是仅供显式测试配置使用的 server 启动脚本;普通 `pnpm dev` 已直接使用 development-only 的 loopback Mind HTTP 配置。支架不模拟真实 Mind 的 Cookie Domain/SameSite/Secure、三级域 CORS/TLS 或 takeover 事务;它只在独立本地页面中暴露 Bright 的手工操作入口。
|
||||
|
||||
`mind-test-harness` 仅参与显式的 `pnpm dev:harness`;它不进入根 `pnpm dev`、`typecheck`、`test` 或 `build`,也不会被 `Dockerfile.server` 复制到生产镜像。完成支架改动无需新增或执行测试;若出现错误,只报告,不阻断主流程。
|
||||
`mind-test-harness` 仅允许在开发环境参与显式的 `NODE_ENV=development pnpm dev:harness`;它不进入根 `pnpm dev`、`typecheck`、`test` 或 `build`,也不会被 `Dockerfile.server` 复制到生产镜像。完成支架改动无需新增或执行测试;若出现错误,只报告,不阻断主流程。
|
||||
|
||||
`VITE_BRIGHT_WEBSOCKET_URL` 用于 Chrome 扩展构建:
|
||||
|
||||
@@ -254,7 +254,7 @@ pnpm dev
|
||||
pnpm dev:server
|
||||
```
|
||||
|
||||
该命令直接启动 server watch,不调用 workspace 级 `with-build-hash`,也跳过 server `predev` 的 contract 构建;因此不会重新构建 Chrome 扩展,也不会改变当前扩展产物中的 `BUILD_HASH`。首次 checkout 或修改 `onetalk-contract` 后,请先执行 `pnpm --filter @trade-message-center/onetalk-contract build`。如果 server 需要本地 Mind 测试支架,请另开终端运行 `pnpm dev:harness`。
|
||||
该命令直接启动 server watch,不调用 workspace 级 `with-build-hash`,也跳过 server `predev` 的 contract 构建;因此不会重新构建 Chrome 扩展,也不会改变当前扩展产物中的 `BUILD_HASH`。首次 checkout 或修改 `onetalk-contract` 后,请先执行 `pnpm --filter @trade-message-center/onetalk-contract build`。如果 server 需要本地 Mind 测试支架,请另开终端在开发环境运行 `NODE_ENV=development pnpm dev:harness`。
|
||||
|
||||
检查 server 是否启动:
|
||||
|
||||
|
||||
@@ -8,15 +8,16 @@ import {
|
||||
type MindMockPermission,
|
||||
type MindMockSessionResult,
|
||||
} from "./model.ts";
|
||||
import { ONETALK_PERMISSIONS } from "@trade-message-center/onetalk-contract";
|
||||
|
||||
const DEFAULT_HOST = "127.0.0.1";
|
||||
const DEFAULT_PORT = 8787;
|
||||
const DEFAULT_CHANNEL_ACCOUNT_ID = "243340382";
|
||||
const DEFAULT_CHANNEL_ACCOUNT_ID = "286995452";
|
||||
const DEFAULT_BINDING = "123";
|
||||
const DEFAULT_AUTHORIZATION_VERSION = "mock-v1";
|
||||
const DEFAULT_MIND_USER_ID = "mind-user-1";
|
||||
const DEFAULT_WORKSPACE_ID = "workspace-1";
|
||||
const DEFAULT_PERMISSIONS = "read,send";
|
||||
const DEFAULT_PERMISSIONS = "read,send,rebuild";
|
||||
const DEFAULT_COOKIE = "mind_session=mock-valid";
|
||||
|
||||
const DEFAULT_HARNESS_HOST = "127.0.0.1";
|
||||
@@ -71,10 +72,14 @@ const parsePermissions = (value: string): MindMockPermission[] => {
|
||||
const permissions = value.split(",").map((permission) => permission.trim());
|
||||
if (
|
||||
permissions.length === 0 ||
|
||||
permissions.some((permission) => permission !== "read" && permission !== "send") ||
|
||||
permissions.some(
|
||||
(permission) => !ONETALK_PERMISSIONS.includes(permission as MindMockPermission),
|
||||
) ||
|
||||
new Set(permissions).size !== permissions.length
|
||||
) {
|
||||
throw new Error("Invalid MIND_MOCK_PERMISSIONS: expected unique read/send values");
|
||||
throw new Error(
|
||||
`Invalid MIND_MOCK_PERMISSIONS: expected unique ${ONETALK_PERMISSIONS.join("/")} values`,
|
||||
);
|
||||
}
|
||||
return permissions as MindMockPermission[];
|
||||
};
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// 定义 Mind mock 的授权与结果模型
|
||||
|
||||
export type MindMockPermission = "read" | "send";
|
||||
import type { OneTalkPermission } from "@trade-message-center/onetalk-contract";
|
||||
|
||||
export type MindMockPermission = OneTalkPermission;
|
||||
|
||||
export type MindMockAuthorization = {
|
||||
binding: string;
|
||||
|
||||
Reference in New Issue
Block a user