@skillbuilder/miniapp-simulator 0.0.0-beta-20261023084421

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/README.md +214 -0
  2. package/assets/phones/Nexus-5X.png +0 -0
  3. package/assets/phones/iPhone-14ProMax.png +0 -0
  4. package/assets/rednote-preview/cover/homebar-1439.jpg +0 -0
  5. package/assets/rednote-preview/cover/homebar-1679.jpg +0 -0
  6. package/assets/rednote-preview/cover/homebar-1919.jpg +0 -0
  7. package/assets/rednote-preview/cover/homebar-2284.jpg +0 -0
  8. package/assets/rednote-preview/cover/homebar-2285.jpg +0 -0
  9. package/assets/rednote-preview/cover/like.png +0 -0
  10. package/assets/rednote-preview/cover/play.png +0 -0
  11. package/assets/rednote-preview/cover/topbar-1439.jpg +0 -0
  12. package/assets/rednote-preview/cover/topbar-1679.jpg +0 -0
  13. package/assets/rednote-preview/cover/topbar-1919.jpg +0 -0
  14. package/assets/rednote-preview/cover/topbar-2284.jpg +0 -0
  15. package/assets/rednote-preview/cover/topbar-2285.jpg +0 -0
  16. package/assets/workbench/index.html +77 -0
  17. package/dist/sw/miniapp-simulator-service-worker.js +855 -0
  18. package/dist/sw/miniapp-simulator-service-worker.min.js +1 -0
  19. package/docs/consumer-guide.md +143 -0
  20. package/docs/electron-review.md +21 -0
  21. package/docs/external-cli-workbench.md +197 -0
  22. package/docs/file-system-design.md +334 -0
  23. package/docs/host-external-api.md +235 -0
  24. package/docs/iframe-service-worker.md +190 -0
  25. package/docs/local-dev.md +106 -0
  26. package/docs/npm-integration-verification.md +45 -0
  27. package/docs/remote-architecture.md +403 -0
  28. package/docs/remote-load-observability.md +268 -0
  29. package/docs/remote-usage.md +371 -0
  30. package/docs/request-design.md +247 -0
  31. package/docs/sdk-usage.md +260 -0
  32. package/docs/server-playground-https-debugging.md +150 -0
  33. package/docs/storage-api-design.md +275 -0
  34. package/docs/tracker.md +339 -0
  35. package/lib/business-line-CMb3ehRn.mjs +15 -0
  36. package/lib/client-BZtD2VtP.mjs +772 -0
  37. package/lib/index-BW85h7kT.mjs +505 -0
  38. package/lib/index.js +10686 -0
  39. package/lib/preload/webview-preload.js +37 -0
  40. package/lib/remote-client.js +11 -0
  41. package/lib/types/business-line.d.ts +7 -0
  42. package/lib/types/capabilities/constants.d.ts +28 -0
  43. package/lib/types/capabilities/csp-snippet.d.ts +32 -0
  44. package/lib/types/capabilities/launch-options.d.ts +7 -0
  45. package/lib/types/capabilities/performance-policy.d.ts +5 -0
  46. package/lib/types/capabilities/security-head-snippet.d.ts +20 -0
  47. package/lib/types/capabilities/security.d.ts +25 -0
  48. package/lib/types/capabilities/style-snippet.d.ts +2 -0
  49. package/lib/types/core/controllers/game-playtest.d.ts +9 -0
  50. package/lib/types/core/controllers/gesture.d.ts +8 -0
  51. package/lib/types/core/controllers/host-api.d.ts +19 -0
  52. package/lib/types/core/controllers/index.d.ts +11 -0
  53. package/lib/types/core/controllers/inspector.d.ts +20 -0
  54. package/lib/types/core/controllers/note-preview.d.ts +9 -0
  55. package/lib/types/core/controllers/permission-sheet.d.ts +7 -0
  56. package/lib/types/core/controllers/widget-cover.d.ts +12 -0
  57. package/lib/types/core/lifecycle.d.ts +7 -0
  58. package/lib/types/core/options.d.ts +33 -0
  59. package/lib/types/core/simulator.d.ts +9 -0
  60. package/lib/types/errors.d.ts +11 -0
  61. package/lib/types/event-bus.d.ts +15 -0
  62. package/lib/types/gesture/host-bus.d.ts +113 -0
  63. package/lib/types/gesture/index.d.ts +27 -0
  64. package/lib/types/gesture/plugins/double-tap.d.ts +10 -0
  65. package/lib/types/gesture/plugins/index.d.ts +27 -0
  66. package/lib/types/gesture/plugins/long-press.d.ts +13 -0
  67. package/lib/types/gesture/plugins/multi-touch-hint.d.ts +11 -0
  68. package/lib/types/gesture/plugins/multi-touch-switch.d.ts +18 -0
  69. package/lib/types/gesture/plugins/selection-disabler.d.ts +11 -0
  70. package/lib/types/gesture/plugins/tap.d.ts +11 -0
  71. package/lib/types/gesture/plugins/touch-emitter.d.ts +11 -0
  72. package/lib/types/gesture/plugins/wheel-scroll.d.ts +11 -0
  73. package/lib/types/gesture/receiver-runtime.d.ts +10 -0
  74. package/lib/types/gesture/receiver-runtime.impl.d.ts +6 -0
  75. package/lib/types/gesture/touch-cursor.d.ts +78 -0
  76. package/lib/types/gesture/touch-dot.d.ts +12 -0
  77. package/lib/types/gesture/types.d.ts +206 -0
  78. package/lib/types/host/shadow-host.d.ts +40 -0
  79. package/lib/types/index.d.ts +49 -0
  80. package/lib/types/injection/guest-log-mark.d.ts +7 -0
  81. package/lib/types/injection/head-builder.d.ts +4 -0
  82. package/lib/types/injection/index.d.ts +19 -0
  83. package/lib/types/injection/modules/console-forward.d.ts +2 -0
  84. package/lib/types/injection/modules/gesture.d.ts +6 -0
  85. package/lib/types/injection/modules/inspector-guest-runtime.d.ts +5 -0
  86. package/lib/types/injection/modules/inspector-pause-runtime.d.ts +6 -0
  87. package/lib/types/injection/modules/inspector.d.ts +8 -0
  88. package/lib/types/injection/modules/jsapi-channel.d.ts +9 -0
  89. package/lib/types/injection/modules/jsapi.d.ts +22 -0
  90. package/lib/types/injection/modules/render-done.d.ts +2 -0
  91. package/lib/types/injection/modules/resource-guard.d.ts +7 -0
  92. package/lib/types/injection/modules/scroll-containment.d.ts +3 -0
  93. package/lib/types/injection/modules/security.d.ts +3 -0
  94. package/lib/types/injection/modules/widget-snapshot.d.ts +13 -0
  95. package/lib/types/injection/preload-builder.d.ts +41 -0
  96. package/lib/types/injection/registry.d.ts +20 -0
  97. package/lib/types/injection/types.d.ts +43 -0
  98. package/lib/types/inspector/host-inspector.d.ts +108 -0
  99. package/lib/types/inspector/index.d.ts +8 -0
  100. package/lib/types/inspector/selector.d.ts +18 -0
  101. package/lib/types/native-ui/devtools/CapabilitiesView.d.ts +18 -0
  102. package/lib/types/native-ui/devtools/DevtoolsPanel.d.ts +24 -0
  103. package/lib/types/native-ui/devtools/LogsView.d.ts +17 -0
  104. package/lib/types/native-ui/devtools/dom.d.ts +40 -0
  105. package/lib/types/native-ui/devtools/index.d.ts +19 -0
  106. package/lib/types/native-ui/devtools/log-entry.d.ts +8 -0
  107. package/lib/types/native-ui/devtools/shortcut.d.ts +12 -0
  108. package/lib/types/native-ui/devtools/tokens.d.ts +14 -0
  109. package/lib/types/native-ui/devtools/types.d.ts +51 -0
  110. package/lib/types/native-ui/index.d.ts +17 -0
  111. package/lib/types/native-ui/note-publish/defaults.d.ts +40 -0
  112. package/lib/types/native-ui/note-publish/index.d.ts +2 -0
  113. package/lib/types/native-ui/note-publish/miniapp-note-publish.d.ts +15 -0
  114. package/lib/types/native-ui/note-publish/note-publish-host.d.ts +15 -0
  115. package/lib/types/native-ui/note-publish/view-data.d.ts +21 -0
  116. package/lib/types/native-ui/permission-sheet/defaults.d.ts +12 -0
  117. package/lib/types/native-ui/permission-sheet/index.d.ts +4 -0
  118. package/lib/types/native-ui/permission-sheet/miniapp-permission-sheet.d.ts +19 -0
  119. package/lib/types/native-ui/permission-sheet/permission-sheet-host.d.ts +23 -0
  120. package/lib/types/native-ui/permission-sheet/types.d.ts +8 -0
  121. package/lib/types/native-ui/rednote-preview/chrome-tone.d.ts +44 -0
  122. package/lib/types/native-ui/rednote-preview/defaults.d.ts +30 -0
  123. package/lib/types/native-ui/rednote-preview/half-sheet-drag.d.ts +61 -0
  124. package/lib/types/native-ui/rednote-preview/index.d.ts +2 -0
  125. package/lib/types/native-ui/rednote-preview/register.d.ts +7 -0
  126. package/lib/types/native-ui/rednote-preview/styles.d.ts +4 -0
  127. package/lib/types/native-ui/rednote-preview/xhs-note-detail-shell.d.ts +70 -0
  128. package/lib/types/native-ui/rednote-preview/xhs-note-half-sheet.d.ts +100 -0
  129. package/lib/types/native-ui/rednote-preview/xhs-profile-shell.d.ts +31 -0
  130. package/lib/types/native-ui/toast/frame-toast-host.d.ts +18 -0
  131. package/lib/types/native-ui/toast/index.d.ts +19 -0
  132. package/lib/types/presets.d.ts +11 -0
  133. package/lib/types/remote/client.d.ts +89 -0
  134. package/lib/types/remote/jsapi-config.d.ts +9 -0
  135. package/lib/types/remote/load-timing.d.ts +46 -0
  136. package/lib/types/remote/protocol.d.ts +160 -0
  137. package/lib/types/remote/review-launch.d.ts +6 -0
  138. package/lib/types/remote/runtime-entry.d.ts +1 -0
  139. package/lib/types/remote/runtime.d.ts +14 -0
  140. package/lib/types/remote/simulator-prompt.d.ts +9 -0
  141. package/lib/types/remote/widget-snapshot-guest-runtime.d.ts +1 -0
  142. package/lib/types/remote-client.d.ts +5 -0
  143. package/lib/types/renderer/base.d.ts +8 -0
  144. package/lib/types/renderer/content-host/iframe-content-host.d.ts +83 -0
  145. package/lib/types/renderer/content-host/index.d.ts +3 -0
  146. package/lib/types/renderer/content-host/types.d.ts +89 -0
  147. package/lib/types/renderer/content-host/webview-content-host.d.ts +53 -0
  148. package/lib/types/renderer/iframe.d.ts +93 -0
  149. package/lib/types/renderer/index.d.ts +2 -0
  150. package/lib/types/renderer/rednote-shell.d.ts +73 -0
  151. package/lib/types/renderer/webview.d.ts +59 -0
  152. package/lib/types/runtime/context.d.ts +31 -0
  153. package/lib/types/runtime/jsapi-modules/context.d.ts +19 -0
  154. package/lib/types/runtime/jsapi-modules/filesystem.d.ts +19 -0
  155. package/lib/types/runtime/jsapi-modules/index.d.ts +9 -0
  156. package/lib/types/runtime/jsapi-modules/interaction.d.ts +4 -0
  157. package/lib/types/runtime/jsapi-modules/location.d.ts +3 -0
  158. package/lib/types/runtime/jsapi-modules/media.d.ts +13 -0
  159. package/lib/types/runtime/jsapi-modules/network.d.ts +3 -0
  160. package/lib/types/runtime/jsapi-modules/storage.d.ts +26 -0
  161. package/lib/types/runtime/jsapi-modules/system.d.ts +4 -0
  162. package/lib/types/runtime/jsapi-modules/types.d.ts +21 -0
  163. package/lib/types/runtime/jsapi-modules/window-info.d.ts +43 -0
  164. package/lib/types/runtime/request/host-request.d.ts +13 -0
  165. package/lib/types/runtime/request/request-manager-frame.d.ts +29 -0
  166. package/lib/types/runtime/request/request-runtime.d.ts +26 -0
  167. package/lib/types/runtime/request/request-types.d.ts +26 -0
  168. package/lib/types/runtime/request/sse-text-framer.d.ts +16 -0
  169. package/lib/types/seal-config.d.ts +134 -0
  170. package/lib/types/sw/index.d.ts +9 -0
  171. package/lib/types/sw/parse-raw.d.ts +34 -0
  172. package/lib/types/sw/protocol.d.ts +17 -0
  173. package/lib/types/sw/served-entry.d.ts +9 -0
  174. package/lib/types/sw/service-worker-content-host.d.ts +11 -0
  175. package/lib/types/sw/service-worker-host.d.ts +73 -0
  176. package/lib/types/sw/service-worker-renderer.d.ts +10 -0
  177. package/lib/types/sw/worker/fs-codec.d.ts +23 -0
  178. package/lib/types/sw/worker/fs-ops.d.ts +22 -0
  179. package/lib/types/sw/worker/fs-store.d.ts +29 -0
  180. package/lib/types/sw/worker/globals.d.ts +49 -0
  181. package/lib/types/sw/worker/main.d.ts +6 -0
  182. package/lib/types/sw/worker/mime.d.ts +2 -0
  183. package/lib/types/sw/worker/mount-store.d.ts +43 -0
  184. package/lib/types/sw/worker/paths.d.ts +4 -0
  185. package/lib/types/sw/worker/serve.d.ts +13 -0
  186. package/lib/types/tracker/constants.d.ts +14 -0
  187. package/lib/types/tracker/default-tracker.d.ts +19 -0
  188. package/lib/types/tracker/index.d.ts +70 -0
  189. package/lib/types/transport/bridge-protocol.d.ts +78 -0
  190. package/lib/types/transport/iframe-transport.d.ts +39 -0
  191. package/lib/types/transport/index.d.ts +14 -0
  192. package/lib/types/transport/jsapi-bridge-messages.d.ts +36 -0
  193. package/lib/types/transport/types.d.ts +48 -0
  194. package/lib/types/transport/webview-transport.d.ts +52 -0
  195. package/lib/types/types/events.d.ts +130 -0
  196. package/lib/types/types/index.d.ts +11 -0
  197. package/lib/types/types/inspector.d.ts +172 -0
  198. package/lib/types/types/jsapi.d.ts +18 -0
  199. package/lib/types/types/options.d.ts +844 -0
  200. package/lib/types/types/renderer.d.ts +145 -0
  201. package/lib/types/utils/ancestor-scroll.d.ts +11 -0
  202. package/lib/types/utils/error-svg.d.ts +12 -0
  203. package/lib/types/utils/frameless-shell.d.ts +114 -0
  204. package/lib/types/utils/miniapp-chrome-svg.d.ts +47 -0
  205. package/lib/types/utils/package-constants.d.ts +8 -0
  206. package/lib/types/utils/screenshot.d.ts +14 -0
  207. package/lib/types/utils/simulator-pc-guard.d.ts +10 -0
  208. package/lib/types/utils/stage-fit.d.ts +6 -0
  209. package/lib/types/utils/touch-emulate.d.ts +26 -0
  210. package/lib/types/utils/zip-entry.d.ts +13 -0
  211. package/lib/types/workbench/index.d.ts +63 -0
  212. package/lib/types/workbench/preview-transition.d.ts +8 -0
  213. package/lib/types/workbench/server/artifact.d.ts +8 -0
  214. package/lib/types/workbench/server/index.d.ts +19 -0
  215. package/lib/types/workbench/server/watch.d.ts +6 -0
  216. package/lib/types/workbench/view.d.ts +36 -0
  217. package/lib/workbench-server.mjs +183 -0
  218. package/lib/workbench.js +1404 -0
  219. package/package.json +63 -0
  220. package/scripts/mock-cli-dev.mjs +52 -0
@@ -0,0 +1,334 @@
1
+ # 模拟器 FS 文件系统方案
2
+
3
+ ## 1. 结论
4
+
5
+ 模拟器文件系统采用“JSAPI + Bridge + Simulator Runtime + Service Worker + IndexedDB”的分层方案:
6
+
7
+ - 小工具通过 `xhs.miniTool` 文件 API 操作文件,不直接访问浏览器存储。
8
+ - Simulator Runtime 负责 JSAPI 路由、实例上下文和错误归一化。
9
+ - Service Worker 是文件系统执行层,负责路径校验、读写、目录、配额、协议 URL 和资源响应。
10
+ - IndexedDB 保存文件字节及元信息,以 `(fsScope, path)` 作为复合主键。
11
+ - `xhsfile://` 只作为端侧兼容的逻辑文件路径被文件 API 解析;浏览器实际请求的是模拟器生成的同源 `/_xhsfile_/` HTTP URL,Service Worker 拦截该 HTTP URL 后从 IndexedDB 返回文件。
12
+
13
+ ## 2. 设计目标
14
+
15
+ - API 语义贴近端侧小工具文件能力。
16
+ - 文件数据与小工具实例、应用身份隔离。
17
+ - 支持 UTF-8、Base64、分片读取、临时文件转持久文件。
18
+ - 生成浏览器可直接加载的同源资源 URL。
19
+ - 对路径穿越、保留名称、容量和单次读写大小进行约束。
20
+ - Service Worker 被浏览器回收或被外部注销后可以恢复。
21
+ - Runtime 与 Service Worker 作为同一 release 发布,协议严格配对。
22
+
23
+ ## 3. 总体架构
24
+
25
+ ```mermaid
26
+ flowchart LR
27
+ A[小工具代码] -->|xhs.miniTool 文件 API| B[JS SDK]
28
+ B -->|Bridge event + callbackId| C[Simulator Runtime]
29
+ C -->|fsInvoke: id + op + reqId + fsScope| D[ServiceWorkerHost]
30
+ D -->|MessageChannel| E[Service Worker]
31
+ E --> F[路径/编码/配额校验]
32
+ F --> G[(IndexedDB miniapp-simulator-fs)]
33
+ A -->|img/video src| H[同源 /_xhsfile_/ URL]
34
+ H -->|fetch 拦截| E
35
+ E -->|Response bytes + MIME| A
36
+ ```
37
+
38
+ ## 4. 分层职责
39
+
40
+ | 层 | 主要职责 |
41
+ | --- | --- |
42
+ | JS SDK | 暴露 Promise/callback API 和 TypeScript 类型,发送标准 Bridge 事件 |
43
+ | Simulator Core | 注册内置文件 handler,注入 `userDataPath`,传递 `appId` 作为 `fsScope` |
44
+ | `file-host-api` | 将 JSAPI 名映射为 `fs*` 操作,归一化存储错误 |
45
+ | `ServiceWorkerHost` | 注册/激活 SW、挂载实例、协议握手、MessageChannel 请求关联、失活恢复 |
46
+ | SW `fs-ops` | 参数、路径、编码、配额校验;执行文件和目录操作 |
47
+ | SW `fs-store` | IndexedDB 事务与 `(scope, path)` 数据访问 |
48
+ | SW fetch handler | 把虚拟文件 URL 转换为浏览器可消费的 HTTP `Response` |
49
+
50
+ ## 5. 文件空间模型
51
+
52
+ ### 5.1 `usr` 持久区
53
+
54
+ - `writeFile`、`appendFile`、`mkdir`、`unlink` 只允许操作 `usr`。
55
+ - `saveFile` 把 `tmp` 文件移动到 `usr`;目标未传时自动生成文件名并保留扩展名。
56
+ - `readFile`、`readDir`、`statFile` 可读取 `usr`。
57
+ - `getFileStorageInfo.usedBytes` 和 `fileCount` 只统计 `usr` 普通文件。
58
+
59
+ ### 5.2 `tmp` 临时区
60
+
61
+ - `writeTempFile` 接收 Base64 Data URI,自动生成 `tmp/<uuid>.<ext>`。
62
+ - `tmp` 可被 `readFile`、`readDir`、`statFile` 读取,不能被 `writeFile`、`appendFile`、`mkdir` 或 `unlink` 直接修改。
63
+ - `saveFile` 以移动语义把临时文件转入 `usr`,成功后原临时路径失效。
64
+ - `getFileStorageInfo.tmpUsedBytes` 单独报告临时区占用。
65
+
66
+ ### 5.3 存储桶隔离
67
+
68
+ `fsScope = appId || mountId`:
69
+
70
+ - 配置稳定 `appId` 时,同一应用的文件使用稳定存储桶,可跨模拟器重建和页面会话访问。
71
+ - 未配置 `appId` 时使用随机 `mountId`,新实例不会获得旧实例的存储桶地址,业务语义为会话级。
72
+ - IndexedDB 使用 `[scope, path]` 复合键,不同应用即使使用相同逻辑路径也不会串数据。
73
+
74
+ ## 6. `xhsfile://` 协议实现
75
+
76
+ ### 6.1 先明确:浏览器不直接拦截 `xhsfile://`
77
+
78
+ 端侧使用以下逻辑路径表达文件:
79
+
80
+ ```text
81
+ xhsfile://usr/drafts/note.json
82
+ xhsfile://tmp/<uuid>.png
83
+ ```
84
+
85
+ 浏览器的 Service Worker 只能拦截其 registration scope 下的 HTTP/HTTPS 请求,不能注册或拦截 `xhsfile://` 自定义 scheme。因此当前实现不存在“浏览器发出 `xhsfile://` 请求,再由 SW 截获”的过程。
86
+
87
+ `xhsfile://` 只承担端侧兼容的逻辑路径语义,并在文件 JSAPI 的 `parseFilePath → extractLogicalPath` 阶段被识别。`usr` 和 `tmp` 是逻辑根目录。文件 API 的入参解析器兼容两种形式:
88
+
89
+ 1. `xhsfile://usr/...`、`xhsfile://tmp/...` 逻辑路径;
90
+ 2. 模拟器生成的同源 `/_xhsfile_/` HTTP URL。
91
+
92
+ 两种输入最终都被归一化为 `usr/...` 或 `tmp/...`,再使用当前请求携带的 `fsScope` 访问 IndexedDB。对于 HTTP URL,解析器还会校验 URL 中编码的 scope 必须等于当前 `fsScope`,避免用一个应用的 URL 越权访问另一个应用的文件。
93
+
94
+ ### 6.2 从逻辑协议到可拦截 URL
95
+
96
+ 为了让 `<img>`、`<video>`、CSS 和 Fetch 能消费运行期文件,模拟器生成位于 SW registration scope 下的同源 HTTP/HTTPS URL:
97
+
98
+ ```text
99
+ https://<simulator-origin>/<sw-scope>/_xhsfile_/<encoded-fsScope>/usr/drafts/note.json
100
+ https://<simulator-origin>/<sw-scope>/_xhsfile_/<encoded-fsScope>/tmp/<uuid>.png
101
+ ```
102
+
103
+ 生成公式:
104
+
105
+ ```ts
106
+ fileUrl(fsScope, path) =
107
+ new URL(`_xhsfile_/${encodeURIComponent(fsScope)}/${path}`, serviceWorkerScope)
108
+ ```
109
+
110
+ 这里使用 `sw.registration.scope` 作为 URL 基址是拦截能够生效的关键:生成结果天然处于该 Service Worker 的控制范围内。
111
+
112
+ URL 有三个来源:
113
+
114
+ - `ServiceWorkerHost.fileUrl(fsScope, "usr")` 生成 `userDataPath`;
115
+ - `writeTempFile` 写入 `tmp` 后返回同源 URL,Core 将其作为 `filePath` 返回;
116
+ - `saveFile` 移动到 `usr` 后返回同源 `savedFilePath`。
117
+
118
+ 模拟器将 `fileUrl(fsScope, "usr")` 注入 `launchOptions.miniToolEnv.userDataPath`。业务代码必须基于 `userDataPath` 拼接路径,不应硬编码 simulator origin、SW scope 或 `fsScope`。
119
+
120
+ ```ts
121
+ const { userDataPath } = window.xhs.launchOptions.miniToolEnv
122
+ const filePath = `${userDataPath}/drafts/note.json`
123
+ ```
124
+
125
+ 直接把 `xhsfile://usr/a.png` 赋给 `<img src>` 不会进入这条链路;DOM 场景必须使用 API 返回的同源 URL或基于 `userDataPath` 拼出的同源 URL。
126
+
127
+ ### 6.3 Service Worker 拦截如何发生
128
+
129
+ 拦截依赖两个前提:
130
+
131
+ 1. `ServiceWorkerHost.acquire` 已通过 `navigator.serviceWorker.register(scriptUrl, { scope })` 注册并等待 worker 激活;
132
+ 2. 页面是该 scope 下、已被 worker 控制的同源页面。worker 在 `activate` 阶段调用 `clients.claim()`,接管 scope 内的现有客户端。
133
+
134
+ 完整读取链路如下:
135
+
136
+ 1. 业务把同源 `/_xhsfile_/` URL 赋给 `<img>`、`<video>` 等元素,浏览器发起普通 GET;不是 `xhsfile://` 请求。
137
+ 2. 因为 URL 位于 SW registration scope 下,GET 触发 worker 的 `fetch` 事件。
138
+ 3. `main.ts` 的 fetch handler 最先调用 `parseXhsFileUrl(event.request.url)`;该分支位于包内资源和远程回源逻辑之前。
139
+ 4. `parseXhsFileUrl` 在 URL pathname 中查找保留标记 `/_xhsfile_/`,把后续内容拆成 `<encoded-fsScope>/<path>`,分别执行安全解码。
140
+ 5. 解析器拒绝空 scope、空 path、NUL、反斜杠和 `..` 路径穿越;不满足虚拟文件格式时返回 `null`,继续执行其他资源路由。
141
+ 6. 命中后立即调用 `event.respondWith(serveXhsFile(scope, path))` 并 `return`,所以不会进入包文件匹配或网络回源。
142
+ 7. `serveXhsFile` 使用 `[scope, path]` 复合键从 IndexedDB 的 `miniapp-simulator-fs/files` 读取 Blob 字节。这条读取链路不依赖内存中的 mount 表,因此 SW 进程重启后仍可读取持久文件。
143
+ 8. 读取到普通文件后,通过文件头 `sniffMediaMime` 识别媒体类型,再由 `serveBytes` 构造 HTTP `Response`,返回 `Content-Type`、`Content-Length` 和禁止缓存的响应头。
144
+ 9. 目录、保留路径、未知 MIME、缺失文件和 IndexedDB 异常统一返回 404。`/_xhsfile_/` 是封闭命名空间,命中该路由后失败也不会透传到真实网络。
145
+
146
+ 核心分派等价于:
147
+
148
+ ```ts
149
+ sw.addEventListener("fetch", (event) => {
150
+ if (event.request.method !== "GET") return
151
+
152
+ const file = parseXhsFileUrl(event.request.url)
153
+ if (file) {
154
+ event.respondWith(serveXhsFile(file.scope, file.path))
155
+ return
156
+ }
157
+
158
+ // 后续才处理包内资源和远程回源
159
+ })
160
+ ```
161
+
162
+ ```mermaid
163
+ sequenceDiagram
164
+ participant Biz as 小工具页面
165
+ participant Browser as 浏览器网络层
166
+ participant SW as Service Worker
167
+ participant IDB as IndexedDB
168
+ Biz->>Browser: img.src = 同源 /_xhsfile_/scope/path
169
+ Browser->>SW: scope 内 GET / fetch event
170
+ SW->>SW: parseXhsFileUrl + 路径校验
171
+ SW->>IDB: get([scope, path])
172
+ IDB-->>SW: Blob bytes
173
+ SW-->>Browser: Response(bytes, MIME, no-store)
174
+ Browser-->>Biz: 图片/视频资源可用
175
+ ```
176
+
177
+ ### 6.4 文件 API 路径解析与资源 GET 是两条入口
178
+
179
+ | 场景 | 输入 | 处理入口 | 是否触发 SW fetch |
180
+ | --- | --- | --- | --- |
181
+ | `readFile`、`writeFile` 等 JSAPI | `xhsfile://usr/a.txt` | Bridge → `fsInvoke` → SW message → `extractLogicalPath` | 否 |
182
+ | `readFile` 等 JSAPI | 同源 `/_xhsfile_/scope/usr/a.txt` URL | Bridge → `fsInvoke` → SW message → `extractLogicalPath`,并校验 scope | 否 |
183
+ | `<img>`、`<video>` 等资源加载 | 同源 `/_xhsfile_/scope/usr/a.png` URL | 浏览器 GET → SW fetch → `parseXhsFileUrl` → `serveXhsFile` | 是 |
184
+ | `<img src="xhsfile://usr/a.png">` | 自定义 scheme | 浏览器自身处理 | 否,模拟器不支持这种 DOM 用法 |
185
+
186
+ JSAPI 调用通过 `MessageChannel` 发送 `FS` 消息到 SW,资源加载通过浏览器 `fetch` 事件进入 SW。两条入口最终共享相同的 `fsScope + path` 数据模型和 IndexedDB 文件记录,但协议入口不同。
187
+
188
+ ### 6.5 为什么使用 Service Worker
189
+
190
+ - 原生 DOM 元素只能消费浏览器支持的 URL,无法识别 `xhsfile://`。
191
+ - Blob URL 生命周期局限于创建页面,难以表达稳定的应用级路径,也不利于跨重载恢复。
192
+ - SW 可以在同源 URL 层返回真实 `Response`,因此 `<img>`、`<video>` 和 Canvas 可以按普通资源使用。
193
+ - FS 与模拟器现有的 ZIP 虚拟文件系统共用同一个版本化 SW,Runtime 和 SW 协议随 release 精确配对。
194
+
195
+ `xhsfile://` 保留端侧路径语义;同源 `/_xhsfile_/` URL 才是浏览器网络层和 Service Worker 实际处理的资源地址。
196
+
197
+ ## 7. 文件 API
198
+
199
+ | API | 输入 | 输出 | 语义 |
200
+ | --- | --- | --- | --- |
201
+ | `writeTempFile` | Base64 Data URI | `filePath` | 解码媒体并写入自动命名的 `tmp` 文件 |
202
+ | `saveFile` | `tempFilePath`、可选 `filePath` | `savedFilePath` | 把 `tmp` 文件移动到 `usr`,目标不能已存在 |
203
+ | `writeFile` | `filePath`、string data、encoding | `writtenBytes` | 覆盖写入 `usr`,自动创建缺失的父目录 |
204
+ | `appendFile` | 同 writeFile | `writtenBytes` | 向 `usr` 文件末尾追加;文件不存在时创建 |
205
+ | `readFile` | `filePath`、encoding、position、length | `data`、`bytesRead`、`eof` | 按字节区间读取 `tmp` 或 `usr` |
206
+ | `readDir` | `dirPath` | `files`、`truncated` | 返回直接子项,不递归 |
207
+ | `statFile` | `filePath` | `size`、`lastModified`、`isDir` | 获取文件或目录元信息 |
208
+ | `unlink` | `filePath` | 空对象 | 删除 `usr` 普通文件,不删除目录 |
209
+ | `mkdir` | `dirPath`、`recursive` | 空对象 | 在 `usr` 创建目录 |
210
+ | `getFileStorageInfo` | 无业务参数 | 容量和分片限制 | 返回 `usr/tmp` 占用及读写限制 |
211
+
212
+ API 支持 Promise 和 `success/fail/complete` 回调两种调用方式;回调只在 SDK 本地消费,不进入 Bridge 消息。
213
+
214
+ ## 8. 写入与读取规则
215
+
216
+ ### 8.1 编码
217
+
218
+ - `writeFile` / `appendFile` 支持 `utf8` 和严格标准 Base64。
219
+ - Base64 必须满足标准字符集、长度和 padding;URL-safe Base64 不被接受。
220
+ - `readFile(..., encoding: 'utf8')` 使用 fatal UTF-8 解码,非法字节返回参数错误。
221
+ - `readFile(..., encoding: 'base64')` 返回标准 Base64 字符串。
222
+ - `writeTempFile` 只接受 `data:<mime>;base64,<body>`。
223
+
224
+ `writeTempFile` 支持 MIME:PNG、JPEG、WebP、GIF、MP4、QuickTime MOV、WebM。
225
+
226
+ ### 8.2 分片读取
227
+
228
+ - `position` 默认为 0,按字节偏移。
229
+ - `length` 默认为 2 MiB,最大 2 MiB。
230
+ - 超过文件末尾返回空数据、`bytesRead: 0`、`eof: true`。
231
+ - UTF-8 分片必须落在合法字符边界,否则 fatal 解码失败;任意字节分片优先使用 Base64。
232
+
233
+ ### 8.3 目录
234
+
235
+ - `readDir` 只返回一级直接子项,并按路径排序。
236
+ - 单次最多返回 1000 项;超出时 `truncated: true`。
237
+ - `mkdir({ recursive: true })` 自动创建缺失父目录;非递归模式要求父目录已存在。
238
+ - 当前没有递归删除目录 API。
239
+
240
+ ## 9. 路径安全
241
+
242
+ | 规则 | 当前值 |
243
+ | --- | --- |
244
+ | 允许根目录 | 读取:`tmp`、`usr`;写入:`usr` |
245
+ | 最大相对路径字节数 | 255 bytes |
246
+ | 最大目录深度 | 4 段 |
247
+ | 单段字符 | `[A-Za-z0-9._-]` |
248
+ | 单段长度 | 1–64 字符 |
249
+ | 禁止内容 | NUL、反斜杠、`.`、`..`、无法解码的 URL |
250
+ | 保留名称 | 任意以 `__xhs` 开头的路径段 |
251
+ | 同源 URL 校验 | URL 内 `fsScope` 必须等于当前调用上下文的 scope |
252
+
253
+ ## 10. 配额与并发
254
+
255
+ | 限制 | 当前值 | 说明 |
256
+ | --- | --- | --- |
257
+ | `usr` 总容量 | 50 MiB / `fsScope` | 覆盖写会扣除被替换文件大小后计算 |
258
+ | 单次写入/追加 | 2 MiB | 按解码后的实际字节计算 |
259
+ | 单次读取 | 2 MiB | `length` 上限 |
260
+ | 单模拟器会话累计写入 | 200 MiB | 防止通过反复覆盖绕过流量上限;UNMOUNT 时释放计数 |
261
+ | FS 操作超时 | 30 秒 | Host 等待 `FS_ACK` 的上限 |
262
+ | 单 release 活跃挂载 | 16 个 | 超限明确失败,不驱逐正在运行的实例 |
263
+
264
+ 所有变更操作进入 SW 内的统一 mutation queue,避免并发写、移动、删除和建目录互相覆盖。读取操作不进入该串行队列。
265
+
266
+ 当前 `tmp` 数据不计入 50 MiB `usr` 配额,`writeTempFile` 也不累计 200 MiB 会话写入限制;临时区只通过 `tmpUsedBytes` 暴露占用,实际仍受浏览器 IndexedDB 配额约束。
267
+
268
+ ## 11. SW 生命周期与恢复
269
+
270
+ 1. `ServiceWorkerHost.acquire` 按 `(scope, scriptUrl)` 单例注册 module SW,触发更新检查并等待 activated。
271
+ 2. Runtime 发送 `MOUNT`,传入实例 ID、文件表、CSP、注入片段和 `fsScope`。
272
+ 3. SW 返回 `MOUNT_ACK`,包含协议版本和支持的 `fsOps`;协议版本必须与 Runtime 完全一致。
273
+ 4. FS 调用使用独立 `MessageChannel`,以 `id + op + reqId` 校验应答,避免并发串包。
274
+ 5. Host 保留 mount 快照。SW 内存状态丢失并返回 `ERR_NOT_MOUNTED` 时,Host 重新注册/激活、重放 MOUNT,并重试一次 FS 调用。
275
+ 6. reload、destroy、pagehide 发送 `UNMOUNT`,释放内存挂载表和本次会话写入计数;IndexedDB 文件按 `fsScope` 保留。
276
+ 7. MOUNT 前清理已不存在 owner client 的孤儿挂载,避免异常退出长期占用 16 个挂载名额。
277
+
278
+ ## 12. 错误模型
279
+
280
+ | 错误码 | 名称 | 场景 |
281
+ | --- | --- | --- |
282
+ | 10001 | INVALID_PARAM | 缺字段、类型/encoding/range/base64/UTF-8 非法 |
283
+ | 50101 | STORAGE_IO_ERROR | IndexedDB 或未预期存储异常 |
284
+ | 50201 | STORAGE_QUOTA_EXCEEDED | 单次写入、`usr` 总容量或会话累计写入超限 |
285
+ | 50202 | FILE_NOT_FOUND | 文件或父目录不存在 |
286
+ | 50203 | PATH_INVALID | 目录不允许、穿越、保留名、路径过深/过长/字符非法 |
287
+ | 50204 | FILE_ALREADY_EXISTS | saveFile 目标已存在,或非递归 mkdir 目标已存在 |
288
+ | 50205 | NOT_A_FILE | 目标是目录,或父路径被普通文件占用 |
289
+ | 50206 | NOT_A_DIRECTORY | readDir 目标不是目录 |
290
+
291
+ 内部 SW 协议还使用 `ERR_NOT_MOUNTED`、`ERR_UNSUPPORTED_OP`、`ERR_MOUNT_LIMIT` 等控制面错误;Runtime 对未知存储异常归一为 50101。
292
+
293
+ ## 13. 与其他能力的关系
294
+
295
+ - `saveImageToPhotosAlbum` 可接收 Data URI,或通过 FS Bridge 读取 `writeTempFile` / `saveFile` 返回的本地路径。
296
+ - `<img>`、`<video>`、Canvas 等通过同源 `/_xhsfile_/` URL 加载可识别媒体。
297
+ - 包内静态资源走 `/<mountId>/<packagePath>`;运行期文件走不含 mountId 的 `/_xhsfile_/<fsScope>/<path>`,两套命名空间独立。
298
+ - 远程应用未命中的包内资源可以回源;`/_xhsfile_/` 未命中固定返回 404,避免污染宿主网络。
299
+
300
+ ## 14. 当前限制
301
+
302
+ - 仅 `iframe-service-worker` 渲染模式提供该浏览器 FS 实现;SW 不可用时文件 JSAPI 不可用。
303
+ - SW 需要安全上下文、同源脚本 URL 和合法 scope。
304
+ - 没有 rename、copy、递归删除、目录删除和随机写 API。
305
+ - DOM URL 只直供能通过文件魔数识别的图片/视频;文本等文件使用 `readFile` 获取。
306
+ - `writeTempFile` 只接受白名单媒体 Base64 Data URI。
307
+ - 文件名不支持空格、中文和白名单外符号。
308
+ - `usr` 文件持久化未提供面向业务的整桶清理 API。
309
+
310
+ ## 15. 验收清单
311
+
312
+ - [x] UTF-8 与 Base64 覆盖写、追加和读取。
313
+ - [x] position/length 分片读取与 EOF 语义。
314
+ - [x] 临时媒体写入、DOM URL 加载、saveFile 移动到 `usr`。
315
+ - [x] 目录创建、直接子项读取、stat 和 unlink。
316
+ - [x] `xhsfile://` 与同源 `/_xhsfile_/` 输入解析一致。
317
+ - [x] appId 存储桶隔离和跨会话访问。
318
+ - [x] 路径穿越、保留名称、深度、长度和编码校验。
319
+ - [x] 单次、总容量、会话累计写入限制。
320
+ - [x] SW 协议版本与能力握手。
321
+ - [x] SW 丢失挂载后自动恢复并重试。
322
+
323
+ ## 16. 代码落点
324
+
325
+ | 模块 | 职责 |
326
+ | --- | --- |
327
+ | `miniapp-js-api/src/apis/fileSystem`(已外移) | 9 个通用文件 API 的类型与 Bridge 封装 |
328
+ | `miniapp-js-api/src/apis/writeTempFile.ts`(已外移) | Base64 Data URI 转临时文件 API |
329
+ | `miniapp-simulator/src/runtime/file-host-api.ts` | JSAPI 到 SW FS op 映射及错误归一化 |
330
+ | `miniapp-simulator/src/sw/protocol.ts` | Runtime/SW 协议版本、操作联合类型 |
331
+ | `miniapp-simulator/src/sw/service-worker-host.ts` | SW 注册、MOUNT、FS RPC、恢复和 UNMOUNT |
332
+ | `miniapp-simulator/src/sw/worker/fs-ops.ts` | 文件操作、路径、编码、配额和 `xhsfile` URL |
333
+ | `miniapp-simulator/src/sw/worker/fs-store.ts` | IndexedDB 存储实现 |
334
+ | `miniapp-simulator/src/sw/worker/main.ts` | message/fetch 分派和 `/_xhsfile_/` 路由 |
@@ -0,0 +1,235 @@
1
+ # 模拟器对外接口 —— 路由 / 小工具封面 / 截图
2
+
3
+ > 面向宿主接入方(创服 PC 等平台)。本文是**接口契约**:宿主可先按本文接入编码,SDK 实现落地后无需调整调用方式。
4
+ >
5
+ > 涉及能力:
6
+ > 1. 页面路由:笔记页 / 小工具页 / 个人页 之间的跳转与状态同步(`redirectTo` + `routerChange`)
7
+ > 2. 小工具封面:`widgetCover.update`(笔记尾卡 + 个人页卡片两处生效;用户信息由 hostApi `getUserInfo` 自动填充)
8
+ > 3. 小工具运行截图:`screenShot()` 主动截图 + `onScreenShot` 回调(base64 / 失败原因)
9
+
10
+ ---
11
+
12
+ ## 一、页面路由
13
+
14
+ ### 1.1 页面枚举
15
+
16
+ ```ts
17
+ type MiniAppSimulatorRoute =
18
+ | "note" // 笔记页
19
+ | "minitool" // 小工具页(运行内容,默认全屏)
20
+ | "profile"; // 个人页
21
+ ```
22
+
23
+ ### 1.2 跳转:`redirectTo`
24
+
25
+ ```ts
26
+ simulator.redirectTo(route: MiniAppSimulatorRoute, options?: {
27
+ sheetMode?: "half" | "full"; // 仅 route === "minitool" 时生效,默认 "full"
28
+ }): Promise<void>;
29
+ ```
30
+
31
+ - 宿主侧切换 Tab 时调用,例:`simulator.redirectTo("profile")`。
32
+ - 跳转完成 resolve;传非法 route 时 reject。
33
+ - 不传 `sheetMode` 时小工具页按**全屏**打开。
34
+
35
+ ```ts
36
+ simulator.getCurrentRoute(): MiniAppSimulatorRoute; // 当前页面:"note" | "minitool" | "profile"
37
+ ```
38
+
39
+ ### 1.3 路由变化回调:`routerChange`
40
+
41
+ 模拟器内部跳转(点头像、点卡片、下滑关闭、命中内存限制被回收等)与宿主调用 `redirectTo` 都会触发,宿主据此保持两侧状态一致:
42
+
43
+ ```ts
44
+ interface MiniAppRouterChangeEvent {
45
+ from: MiniAppSimulatorRoute;
46
+ to: MiniAppSimulatorRoute;
47
+ }
48
+ ```
49
+
50
+ 两种注册方式(与现有 `onLoad` / `onError` 同一套机制):
51
+
52
+ ```ts
53
+ // 方式一:事件级(on / once / off 三件套)
54
+ const off = simulator.on("routerChange", (e) => {
55
+ // e.from / e.to
56
+ });
57
+
58
+ // 方式二:option 级,构造时绑定
59
+ createMiniAppSimulator({
60
+ onRouterChange(e) {
61
+ // e.from / e.to
62
+ },
63
+ });
64
+ ```
65
+
66
+ 注意:**宿主自己调用 `redirectTo` 也会收到 `routerChange` 回声**。如需区分是否自己发起,可在调用前后自行打标(`redirectTo` 返回 Promise,天然可界定)。
67
+
68
+ 典型用法 —— 宿主 Tab 与模拟器双向同步:
69
+
70
+ ```ts
71
+ // 模拟器 → 宿主:内部跳转时同步宿主 Tab
72
+ simulator.on("routerChange", ({ to }) => setHostTab(to));
73
+
74
+ // 宿主 → 模拟器:宿主 Tab 切换时驱动模拟器
75
+ onHostTabChange((tab) => simulator.redirectTo(tab));
76
+ ```
77
+
78
+ 内部触发路由变化的已知场景:
79
+
80
+ | 场景 | 变化 |
81
+ | --- | --- |
82
+ | 笔记页点击导航栏头像 | `note → profile` |
83
+ | 笔记页 / 个人页点击小工具卡片 | `note/profile → minitool` |
84
+ | 全屏小工具页返回、半屏下滑关闭 | `minitool → note` |
85
+ | 小工具命中内存限制被回收 | `minitool → note` |
86
+
87
+ ---
88
+
89
+ ## 二、小工具封面:`widgetCover.update`
90
+
91
+ 小工具封面是**一个小工具的属性**:设置了它,笔记轮播「小工具尾卡」和个人页「作品 → 小工具」卡片**两处同时生效**。个人页卡片的用户信息(头像 / 昵称)由 SDK 通过 hostApi `getUserInfo` 自动填充,宿主无需调用任何接口。
92
+
93
+ ```ts
94
+ interface MiniAppWidgetCoverController {
95
+ /** 部分字段更新(title / cover 单独覆盖) */
96
+ update(data: Partial<XhsWidgetCoverData>): void;
97
+ }
98
+
99
+ interface XhsWidgetCoverData {
100
+ /** 标题;不传默认「小工具模拟器」 */
101
+ title?: string;
102
+ /** 封面图 url / dataURL;不传用 SDK 自动截图结果(内部按 3:4 顶部对齐裁剪展示) */
103
+ cover?: string;
104
+ }
105
+ ```
106
+
107
+ 正常链路(宿主无需干预):部署成功 → SDK 自动截图 → 内部裁 3:4 → 尾卡 + 个人页卡片自动更新;完整截图经 `onScreenShot`(source: "auto")回调宿主。
108
+
109
+ 宿主对自动截图不满意,自己传一张(部分字段覆盖,title 不受影响):
110
+
111
+ ```ts
112
+ simulator.widgetCover.update({ cover: hostCustomCover });
113
+ // 笔记尾卡 ← 覆盖
114
+ // 个人页卡片 ← 覆盖
115
+ ```
116
+
117
+ 也可在构造时传入初始数据:
118
+
119
+ ```ts
120
+ createMiniAppSimulator({
121
+ widgetCover: { title: "汇率计算器", cover },
122
+ });
123
+ ```
124
+
125
+ ---
126
+
127
+ ## 三、截图:`screenShot` / `onScreenShot`
128
+
129
+ ### 3.1 结果结构
130
+
131
+ ```ts
132
+ type MiniAppScreenShotResult =
133
+ | {
134
+ success: true;
135
+ /** 完整 dataURL(data:image/png;base64,xxx),可直接用于 <img> 或上传 */
136
+ base64: string;
137
+ width: number;
138
+ height: number;
139
+ /** auto = 部署成功后自动截图;manual = 宿主主动调用 screenShot() */
140
+ source: "auto" | "manual";
141
+ }
142
+ | {
143
+ success: false;
144
+ code:
145
+ | "not-loaded" // 小工具尚未加载
146
+ | "render-failed" // 页面渲染异常,截图内容不可用
147
+ | "timeout" // 截图超时
148
+ | "unsupported"; // 当前环境不支持截图
149
+ /** 人类可读的失败原因 */
150
+ reason: string;
151
+ };
152
+ ```
153
+
154
+ ### 3.2 主动截图
155
+
156
+ ```ts
157
+ simulator.screenShot(): Promise<MiniAppScreenShotResult>;
158
+ ```
159
+
160
+ 返回**完整截图**(整页画面,不裁剪),成功返回 base64,失败返回原因;**不抛异常**(除非法入参),失败信息在 result 里。结果同时以 `screenShot` 事件(`source: "manual"`)回调。
161
+
162
+ ### 3.3 自动截图回调
163
+
164
+ ```ts
165
+ createMiniAppSimulator({
166
+ // 部署成功后 SDK 自动截图并回调 onScreenShot(source: "auto"),失败也会回调失败原因
167
+ onScreenShot(result) {
168
+ if (result.success) uploadCover(result.base64);
169
+ else showUploadPlaceholder(result.reason); // 没截到图 → 引导用户手动上传封面
170
+ },
171
+ });
172
+
173
+ // 事件级订阅(manual 截图也会触发该事件)
174
+ simulator.on("screenShot", (result) => {});
175
+ ```
176
+
177
+ 说明:
178
+
179
+ - 回调结果以 `source: "auto" | "manual"` 区分是部署后自动截图还是宿主主动调用。
180
+ - 笔记轮播「小工具尾卡」的内部封面(SDK 自行裁剪 3:4)不受对外结果影响,始终自动更新。
181
+ - 已有 `simulator.capture(): Promise<Blob>` 保持不变;`screenShot()` 是面向封面上传场景的新入口,产出 base64 + 失败原因。
182
+
183
+ ### 3.4 典型链路(创服封面上传)
184
+
185
+ ```ts
186
+ const simulator = createMiniAppSimulator({
187
+ onRouterChange({ to }) {
188
+ setHostTab(to);
189
+ },
190
+ });
191
+
192
+ // 部署成功后:截图 → 上传 → 设置封面(尾卡 + 个人页卡片两处生效)
193
+ await simulator.loadZip(file);
194
+ const shot = await simulator.screenShot();
195
+ if (shot.success) {
196
+ await uploadCover(shot.base64);
197
+ simulator.widgetCover.update({ title: "汇率计算器", cover: shot.base64 });
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## 四、远程模式(`@skillbuilder/miniapp-simulator/remote`)
204
+
205
+ `createRemoteMiniAppSimulator` 的返回值镜像透出以上全部能力,用法与本地模式一致:
206
+
207
+ ```ts
208
+ const remote = createRemoteMiniAppSimulator({ /* ... */ });
209
+
210
+ remote.redirectTo("profile");
211
+ remote.getCurrentRoute();
212
+ remote.widgetCover.update({ title, cover });
213
+ remote.screenShot(); // Promise<MiniAppScreenShotResult>
214
+ remote.on("routerChange", (e) => {}); // 事件转发
215
+ remote.on("screenShot", (result) => {});
216
+ ```
217
+
218
+ 远程选项 `options`(`RemoteMiniAppSimulatorOptions.options`)中同样支持 `widgetCover` 数据、`onRouterChange` / `onScreenShot` 回调(回调经协议转发回宿主侧触发)。
219
+
220
+ > 远程协议版本将升到 7;客户端与 Runtime 版本不匹配时握手会直接报错,升级 SDK 时请两端同步发版。
221
+
222
+ ---
223
+
224
+ ## 五、能力总览(新增部分)
225
+
226
+ | 接口 | 说明 |
227
+ | --- | --- |
228
+ | `redirectTo(route, options?)` | 跳转页面(note / minitool / profile),minitool 默认全屏 |
229
+ | `getCurrentRoute()` | 获取当前页面(note / minitool / profile) |
230
+ | `on("routerChange", handler)` / `onRouterChange` | 任意来源的页面切换回调 `{ from, to }` |
231
+ | `widgetCover.update(data)` | 设置小工具封面(title / cover),笔记尾卡 + 个人页卡片两处生效 |
232
+ | `screenShot()` | 主动截图(完整画面),成功返回 base64,失败返回 code + reason |
233
+ | `on("screenShot", handler)` / `onScreenShot` | 截图结果回调(`source: "auto" \| "manual"` 区分来源) |
234
+
235
+ 个人页卡片作者行的用户信息(头像 / 昵称)无对外接口,由 SDK 经 hostApi `getUserInfo` 自动填充。