# 自定义 H5 模块与统一界面协议(UI Bridge v1 / 底座 v0.9) ## 唯一界面模式:自定义 H5 从 v0.9 起,所有业务应用必须在清单中声明 ui.mode=h5 和完整资源白名单;缺少 ui、generated 或其他模式一律拒绝安装和注册。底座不再根据字段生成表单、列表、工作流页或通讯录页。 统一工作空间保留登录、侧栏、应用菜单、顶栏、主题及账号操作。模块自己编写 HTML/CSS/JS,通过 PlatformUI 访问本模块动作。业务 RPC 仍为协议1,后端继续使用受信任的原生模块;数据、权限、角色、API Key 和会话规则不变。 Descriptor 仅描述 ID、Name、Version、Protocol、Description 和可选 Actions;Action 仅含 Name、Label。SDK 已移除 Field、Descriptor.Fields/View/CreateLabel、Action.Fields/Statuses/OwnerOnly;manifest 不再提供 record_columns。字段校验、状态、所有权和修订冲突由后端 Execute 负责,表单控件、表格列和按钮状态由各应用 H5 负责。旧二进制返回的额外界面元数据不再解析。 ```json { "id": "cms", "name": "内容管理", "version": "0.3.0", "protocol": 1, "entry": "cms", "capabilities": ["identity.get", "records.list", "records.create"], "ui": { "mode": "h5", "entry": "ui/cms.html", "bridge_version": 1, "files": ["ui/cms.html", "ui/cms.css", "ui/cms.js"] } } ``` 清单原有权限和角色模板仍有效,上例省略这些可选字段以便展示。Windows 入口使用 `cms.exe`。后端只描述身份、版本、协议和可用动作,并校验所有业务输入,不声明界面字段。 ## 安装包与发布 ZIP 根目录包含 manifest.json、原生入口、可选 simulation.json;H5 资源放在 ui/,每个文件必须在 ui.files 明确列出。最多128个资源,每个不超过2 MiB,资源总计不超过8 MiB,整个 ZIP 压缩和解压总计仍限64 MiB。 只接受 ASCII 路径段,禁止绝对路径、空段、反斜杠、冒号、`.`/`..`、符号链接、未声明文件、大小写冲突路径、Windows 设备名及以点结尾的路径段。HTML、CSS、JS、MJS、JSON、PNG/JPEG/WebP/GIF/SVG/ICO、WOFF/WOFF2/TTF 为允许类型;文本使用 UTF-8,HTML 必须包含 head 元素。不要打包源码、node_modules、source map 或目录条目。 资源与二进制共同计入原有包 SHA256,模拟只使用候选包内容。批准后整体安装,失败整体回退;前端不会绕过发布审核单独更新。模拟验证包结构及原业务行为,浏览器交互与视觉仍需另外验收。打包工具 scripts/package_module.py 已支持清单声明的资源。 ## 浏览器隔离与资源访问 采用 `iframe sandbox="allow-scripts allow-forms"`,**不授予 allow-same-origin**,因此应用文档具有不透明来源(消息 origin 为 null)。allow-forms 保留表单校验和 JavaScript submit 事件,CSP 的 form-action 'none' 仍禁止表单向网络提交。资源可部署在同一平台,无需额外域名,但浏览器不允许页面访问父窗口 DOM、Cookie 或父页面存储。服务端也以 CSP sandbox 约束直接打开的资源文档。 平台通过浏览器会话和 CSRF 创建 `/api/modules/{id}/ui-session`,取得短期资源 URL。URL 中的随机票据仅允许读取该模块的声明资源,不允许调用 API,也不是个人 API Key。票据绑定会话、账号权限、模块运行实例,最长30分钟;退出、停用、撤权、模块停止/重启/替换后失效。切换应用或离开页面时主动关闭票据。资源不缓存,不发送 Referrer;不透明来源的资源请求只获得 `Access-Control-Allow-Origin: null`,不允许凭据 CORS。 CSP 禁止直接 fetch/XHR/WebSocket、外部脚本/CDN、嵌套框架、Worker、对象插件、表单原生提交和 base 重写。iframe 不允许弹窗、顶层跳转、下载或设备权限。所有业务数据走消息桥;资源引用应为相对路径,SPA 使用 hash 路由。构建工具设置相对 base,依赖需随包发布。不要使用 eval、动态字符串执行或 Service Worker。 这是浏览器前端隔离,不是原生后端程序沙箱,也不能保证恶意 JavaScript 不消耗浏览器 CPU。正式部署仍须审查界面内容和原生程序;后端 Wasm/容器隔离是另一项工作。 ## 平台自动注入的 JavaScript SDK 平台在 HTML head 的最前部注入主题样式和 `window.PlatformUI`。模块不需要手工加载桥脚本,不应覆盖该对象。开发者可使用下载 SDK 中的 h5/ 示例。 ```javascript const platform = window.PlatformUI; const context = await platform.ready; // {bridge_version, module:{id,name,version}, actor:{id,name}, actions, theme} const records = await platform.execute('list'); const requestId = crypto.randomUUID(); await platform.execute('create', {title:'示例'}, requestId); await platform.notice('保存成功'); const latest = await platform.context(); const unsubscribe = platform.onTheme(theme => { // 可在这里更新图表;基础 CSS 变量和 data-theme 会自动同步。 }); await platform.resize(900); // 平台限制在400至1800px ``` execute 返回后端 Response.data;失败抛出带 code 的 Error。写入超时可能已经提交,使用同一 requestId 和相同内容重试。SDK 默认20秒超时,平台每个框架最多8个并发调用、每分钟60次调用、单消息最多64 KiB、单回复最多1 MiB。 业务桥提供 context.get、actions.execute、api.request、notice.show、frame.resize;另有受限的标题展示消息 header.set。调用不接受 URL、目标模块 ID 或调用人身份,不能访问用户管理、其他模块、任意平台 HTTP 接口。动作必须属于当前模块的 list/create 或声明的工作流动作,同时符合用户权限;HTTP 与反向 RPC 仍执行服务端校验。页面上下文不包含 API Key、CSRF、邮箱或其他账号的资料。 握手仅接收当前 iframe.contentWindow 发出的 null 来源消息,随后使用专属 MessageChannel 通信。父窗口给不透明来源转交端口时使用 `*`,接收端仍检查父窗口身份与精确来源。导航、重连时更换端口,旧端口关闭,迟到回复不会投递到新页面。 ## 统一体验 底座负责账号、主导航、应用标题与全局提示;模块不要重复绘制登录页、平台侧栏或账号系统。自定义页面仍遵守 adminui-v1,使用 --c-canvas、--c-surface、--c-text、--c-text-muted、--c-border、--c-brand 等变量,支持深浅主题与手机宽度。平台自动注入基础字体和主题变量,主题切换时通过消息桥同步。 CMS 0.3.0 是完整示例:内容卡片、统计、状态筛选、撰稿弹窗、阅读及审核操作均由 H5 绘制,业务数据、权限和审核规则继续由原 CMS 后端处理。工单、报销、请假、通讯录、发票与五子棋也都有独立的 H5 资源包。 ## v0.8 → v0.9 迁移和验收 这是界面契约的收敛升级。升级底座前必须把所有在用模块补齐 H5 资源;不再提供自动界面降级。先在隔离环境注册并检查每个应用,再整体升级底座和模块包。保持模块ID、权限ID、角色ID、集合名和数据目录不变,存量业务无需转换。保留旧底座和完整旧模块目录,回退时一起恢复,避免新底座与旧模块混用。 五子棋和发票的后端源码不在本仓库,此次保留已批准的 Linux amd64 原二进制及原版本号,SHA256 来源见各应用 backend/provenance.json。Go 插件传输仍可读取其身份和动作,额外的旧界面字段被忽略;表单和棋盘全部由新 H5 实现。没有伪造 Windows 后端。这两个应用的界面升级由整个包 SHA256 区分。 可写应用的 simulation.json 包含 valid_payload、conflict_payload,可显式提供 invalid_payload(缺省为 {}),后端必须拒绝无效样例。模拟不再读取字段定义来推断校验。只读通讯录继续走其只读权限、组织和重启验收。浏览器还需验收主题、手机布局、只读角色、幂等重试以及正常业务交互。 ## v0.17.4:业务界面只保留一层应用标题 底座顶栏是唯一的应用标题区。原 BUSINESS 模块介绍标题、连接工具卡片和包裹整个 H5 的外框已合并;下方直接呈现应用统计、列表、表单和业务分区。统计卡片与列表小标题仍保留。 上下文新增 `presentation: {header: "platform"}`,已有字段与业务接口不变。注入的桥接脚本会兼容现有应用的顶层 `body > header`、`body > main > header`、`#app > .page > header`;新应用可用 `data-platform-header` 标记包含 h1 的应用标题区。应用说明、欢迎信息和连接状态以纯文本同步到顶栏,原生 button/select 及现有连接按钮同步为顶栏操作;原业务事件、确认弹窗、校验和写请求号均仍由应用处理。 标题内容经当前 iframe 的专属 MessageChannel 发送 `header.set`,父层确认接受后才隐藏原应用标题。未支持的布局或同步失败时保留应用原标题;不使用跨域 DOM 访问、不取消 iframe 沙箱。文本最多800字符、最多12个操作、每个下拉最多100个选项;父层不接收HTML、外部链接、调用人或其他模块ID,标题同步限每秒4次并与业务每分钟60次预算分开。 原操作的隐藏、禁用与选中状态会同步;应用打开原生 dialog 后顶栏业务操作暂时禁用,避免跨 iframe 点击绕过弹窗状态。顶栏只转交点击/选择事件,不直接执行写入、不代替业务权限或审核。切换应用、返回工作台及重新连接会清理旧标题操作和通道。 本次通过底座统一兼容现有9个应用,模块包、后端、数据结构和权限声明均保持原样。自定义复杂控件和业务筛选仍可留在内容区,不会自动转成底座生成表单。