@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,260 @@
1
+ # Designable Miniapp Simulator SDK 使用文档
2
+
3
+ ## 设计定位
4
+
5
+ SDK 核心只渲染 **干净的手机本体(设备舞台)**:手机壳 + 内容层 + 内置 overlay(权限 Sheet / REDNote 壳层 / toast)。
6
+ 顶栏、侧栏、授权触发按钮等 chrome 由消费场景(PC 预览 / 审核模拟器)自行实现,全部通过 **实例方法** 驱动模拟器。
7
+
8
+ - **挂载**:只需提供一个 `mount` root 节点,SDK 在其内部创建自己的宿主元素,默认用 **Shadow DOM** 隔离样式。
9
+ - **切换**:外壳、内容模式、设备、视口、能力开关都以语义化实例方法暴露(`switchMode` / `switchPhoneFrame` / `switchDevice` / `setViewport` ...)。
10
+ - **回调化**:加载链路提供 `beforeLoad` → `load` → `renderDone` 事件与同名 option 回调。
11
+
12
+ ## Core SDK
13
+
14
+ ```ts
15
+ import { createMiniAppSimulator } from "@skillbuilder/miniapp-simulator";
16
+
17
+ const simulator = createMiniAppSimulator({
18
+ // 挂载目标:CSS 选择器或 HTMLElement,SDK 在其内部创建 shadow host
19
+ mount: "#preview",
20
+ // Shadow DOM 样式隔离,默认 true;传 false 退化为 light DOM
21
+ shadow: true,
22
+ // Web 运行时:Service Worker 虚拟服务器(默认)。Electron 用 "webview"
23
+ renderer: "iframe-service-worker",
24
+ // SW 模式需消费方自托管 SW 脚本(同源)
25
+ serviceWorker: { scriptUrl: "/miniapp-sw.js" },
26
+ viewport: { width: 390, height: 844 },
27
+ query: {
28
+ source: "preview",
29
+ },
30
+ // 生命周期回调(等价于对应 simulator.on(...),实例创建即绑定)
31
+ onBeforeLoad: (event) => console.log("beforeLoad", event.url, event.source),
32
+ onLoad: (event) => console.log("loaded", event.duration),
33
+ onRenderDone: (event) => console.log("renderDone", event.duration),
34
+ onError: (error) => console.error(error.code, error.message),
35
+ });
36
+
37
+ // create 只配置;入口由 loadApp / loadZip / loadFiles 加载
38
+ await simulator.loadApp("http://127.0.0.1:5173/demo-app.html");
39
+ ```
40
+
41
+ ## 实例方法(切换 / 加载)
42
+
43
+ 无需销毁重建,所有"切换"通过方法完成:
44
+
45
+ ```ts
46
+ // 内容模式:half / fullscreen(switchMode 为别名)
47
+ await simulator.switchContentMode("half");
48
+ await simulator.switchMode("fullscreen");
49
+
50
+ // 切换 / 关闭手机外壳(会重建渲染器并重新加载)
51
+ await simulator.switchPhoneFrame("nexus-5x");
52
+ await simulator.switchPhoneFrame(false);
53
+
54
+ // 设备预设(联动逻辑视口,仅 resize 不重载)
55
+ simulator.switchDevice("iphone-15-pro-max");
56
+
57
+ // 直接设置逻辑视口(resize 的语义化别名)
58
+ simulator.setViewport({ width: 430, height: 932 });
59
+
60
+ // 加载新的小工具 URL(可附带 query)
61
+ await simulator.loadApp("https://cdn.../index.html", { query: { versionId: "v2" } });
62
+
63
+ // 自动加载 .zip 工具包(解压 + 交 SW 虚拟服务器直供 + 加载,一步到位)
64
+ await simulator.loadZip(zipFile, { jszip: JSZip });
65
+
66
+ // 加载目录文件表(AI 出码目录产物等无 zip 打包场景)
67
+ await simulator.loadFiles({ "index.html": "<!doctype html>...", "app.js": "..." });
68
+
69
+ // 能力开关(会重新加载渲染器)
70
+ await simulator.setTouchEmulation(true);
71
+ ```
72
+
73
+ ## 加载 .zip 工具包(`loadZip`)
74
+
75
+ `loadZip` 把「解压 → 原始文件表挂载给 Service Worker(同源虚拟服务器)→ iframe 直连虚拟 URL」收敛为一个实例方法,
76
+ 入参支持 `Blob | File | ArrayBuffer | Uint8Array`,返回包解析结果便于业务侧展示。
77
+ > `loadZip` / `loadFiles` 在 `renderer: "iframe-service-worker"`(默认)下可用,需配 `serviceWorker.scriptUrl`。
78
+
79
+ ```ts
80
+ import { createMiniAppSimulator } from "@skillbuilder/miniapp-simulator";
81
+ import JSZip from "jszip"; // 或在全局挂载 window.JSZip(CDN / UMD),不传 jszip 时自动回退
82
+
83
+ const simulator = createMiniAppSimulator({ mount: "#preview" });
84
+ await simulator.load();
85
+
86
+ const result = await simulator.loadZip(zipFile, {
87
+ jszip: JSZip, // JSZip 兼容对象(含静态 loadAsync);缺失时回退 window.JSZip
88
+ // entry: "index.html", // 入口 HTML 文件名,默认 index.html
89
+ // query: { source: "zip-preview" }, // 追加 / 覆盖到加载 URL 的 query
90
+ });
91
+
92
+ result.files; // 包内文件清单 [{ path, size }]
93
+ result.entry; // 实际命中的入口 HTML 路径
94
+ ```
95
+
96
+ - **技术栈无关**:本包不强依赖 JSZip。通过 `{ jszip }` 注入 JSZip 兼容对象,或在全局挂载 `window.JSZip`;二者皆缺失时抛出 `MiniAppSimulatorError`(code `ZIP_LOAD_FAILED`)。
97
+ - 包内缺少入口 HTML 时抛出 code `ZIP_NO_ENTRY`。
98
+ - 解压后的文件通过 Service Worker 挂载为同源虚拟路径,下次加载或销毁时自动卸载。
99
+ - 解压资源经过 ZIP 路径穿越防护(`..` 条目直接拒绝)。
100
+
101
+ ## 设备能力枚举(`MiniAppCapability` / 元数据)
102
+
103
+ `deviceCapabilities.sealAllow` 接受 `MiniAppCapability` 枚举值(白名单语义)。支持的能力连同**受控方 / 说明 / 关联 permission**已结构化导出,可直接用于配置面板渲染与白名单校验:
104
+
105
+ ```ts
106
+ import {
107
+ MiniAppCapability,
108
+ MINI_APP_CAPABILITIES, // 全量能力元数据表(唯一事实源)
109
+ SEAL_CONTROLLED_CAPABILITIES, // 受 sealAllow 白名单实际控制的能力
110
+ HOST_CONTROLLED_CAPABILITIES, // 由宿主层控制的能力(camera / microphone / photo-library)
111
+ isMiniAppCapability, // 校验单个值是否为合法枚举取值
112
+ sanitizeCapabilityList, // 清洗 allow 列表:剔除非枚举项 + 去重
113
+ } from "@skillbuilder/miniapp-simulator";
114
+
115
+ // 渲染「能力开关」面板
116
+ const panel = MINI_APP_CAPABILITIES.map((cap) => ({
117
+ value: cap.value,
118
+ label: cap.description,
119
+ controlledBy: cap.controlledBy, // "seal" 才真正受 sealAllow 控制
120
+ }));
121
+
122
+ // 把不可信的外部配置归一化后再下发
123
+ await simulator.setDeviceCapabilities({
124
+ sealAllow: sanitizeCapabilityList(userSelectedCapabilities),
125
+ });
126
+ ```
127
+
128
+ ## Debug 调试面板(`debug`)
129
+
130
+ 由模拟器开发者通过入参 `debug: true` 控制开启(默认 `false`,不影响生产消费方)。开启后:
131
+
132
+ ```ts
133
+ const simulator = createMiniAppSimulator({
134
+ mount: "#preview",
135
+ debug: true, // 开启内置调试面板
136
+ });
137
+ await simulator.loadApp("https://example.com/demo-app.html");
138
+ ```
139
+
140
+ - **唤起快捷键**:`Shift + Command + U`(macOS)/ `Shift + Ctrl + U`(其他平台)。再次按下或点击右上角 `×` 关闭。
141
+ - **布局**:面板以玻璃拟态形态、`position: fixed` 停靠在**窗口右侧**(类似浏览器 DevTools 右侧停靠),挂在 `document.body`,**不进入模拟器 mount 容器、不压缩或遮挡手机舞台**。
142
+ - 面板包含两个 Tab:
143
+
144
+ | Tab | 能力 |
145
+ | --- | --- |
146
+ | 能力管控 | 枚举全部 `MiniAppCapability`,按 `seal` / `host` 分组展示当前**放行 / 已封印**状态。每项开关切换即重算 `sealAllow` 白名单并调用 `setDeviceCapabilities` **整页重新加载**小应用(seal 封印为加载期一次性生效)。附「全部放行 / 全部封印 / 恢复默认」。Worker / WebSocket 等由 CSP 指令管控的能力不在此面板,被拦时看 console 的 CSP 违规日志。 |
147
+ | 运行日志 | 订阅模拟器事件流,实时滚动展示生命周期、`console`、`navigation`(被拦截跳转高亮)等关键事件,支持文本过滤、暂停滚动、清空。`network` 事件在采集到时一并展示。 |
148
+
149
+ > 提示:当焦点位于小应用 iframe 内部时,父文档收不到快捷键,需先点击手机外区域再按快捷键。
150
+
151
+ ## 生命周期事件
152
+
153
+ | 事件 | 触发时机 | payload |
154
+ | --- | --- | --- |
155
+ | `ready` | 渲染容器初始化完成 | `{ renderer, viewport }` |
156
+ | `beforeLoad` | 即将加载(renderer.load 之前) | `{ url, contentMode, source }` |
157
+ | `load` | URL 加载完成(iframe/webview onload) | `{ url, duration }` |
158
+ | `renderDone` | 小应用首屏渲染完毕(子文档首帧回报) | `{ url, duration }` |
159
+ | `error` / `navigation` / `console` / `network` / `destroy` | 同既有定义 | —— |
160
+
161
+ `renderDone` 通过 SW 直供 HTML 中注入的 bootstrap 在子文档 `DOMContentLoaded` 后下一帧回报;webview 时以 `load` 近似。
162
+
163
+ ## Shadow DOM 隔离
164
+
165
+ 默认 `shadow: true`,SDK 在 `mount` root 内创建一个宿主元素并 `attachShadow`,手机壳 / 内容层 / 缩放手柄 / 权限 Sheet / REDNote 壳层 / jsapi toast 全部渲染在 shadow root 内,不受消费方页面 CSS 影响,也不会污染宿主页面。需要调试或在不支持 Shadow DOM 的环境运行时可传 `shadow: false` 退化为 light DOM。
166
+
167
+ ## 内容模式(`contentMode`)
168
+
169
+ 通过 `contentMode` 切换手机框内的展示形态:
170
+
171
+ | `contentMode` | 说明 |
172
+ | --- | --- |
173
+ | `fullscreen`(默认) | 启动即全屏、右侧滑入、顶部返回键;不可下滑收起,点返回键收起露出笔记详情页背景 |
174
+ | `half` | 笔记详情顶栏 + 背景 + **底部半屏 Sheet 内嵌 iframe**;下滑收起、上滑展开全屏 |
175
+
176
+ ```ts
177
+ import { createMiniAppSimulator } from "@skillbuilder/miniapp-simulator";
178
+
179
+ // 半屏:小工具在半屏 iframe 中打开
180
+ const halfSimulator = createMiniAppSimulator({
181
+ mount: "#preview",
182
+ contentMode: "half",
183
+ notePreview: {
184
+ user: { avatar: "https://...", nickname: "Purple阿紫" },
185
+ title: "笔记标题",
186
+ descHtml: "正文",
187
+ images: [{ url: "https://...", ratio: 1 }],
188
+ },
189
+ rednoteDetail: {
190
+ heightRatio: 0.85,
191
+ placeholderText: "部署成功后可预览",
192
+ },
193
+ viewport: { width: 390, height: 844 },
194
+ });
195
+
196
+ // 全屏:小工具占满设备、右侧滑入
197
+ const fullscreenSimulator = createMiniAppSimulator({
198
+ mount: "#full-preview",
199
+ contentMode: "fullscreen",
200
+ viewport: { width: 390, height: 844 },
201
+ });
202
+
203
+ // 先出壳层(无入口时半屏显示 placeholderText「部署成功后可预览」)
204
+ await halfSimulator.load();
205
+ // 有入口时加载进半屏 iframe
206
+ await halfSimulator.loadApp("http://127.0.0.1:5173/demo-app.html");
207
+
208
+ // 运行时更新壳层数据(无需 reload)
209
+ halfSimulator.notePreview.update({ title: "新标题" });
210
+
211
+ // 仅切换顶栏用户头像与昵称
212
+ halfSimulator.notePreview.updateUser({
213
+ avatar: "https://example.com/avatar.png",
214
+ nickname: "Purple阿紫",
215
+ });
216
+
217
+ // 切换 contentMode 会自动 reload
218
+ halfSimulator.updateConfig({ contentMode: "fullscreen" });
219
+ await halfSimulator.loadApp("https://example.com/demo-app.html");
220
+ ```
221
+
222
+ 手机外壳是模拟器内置能力,**默认无贴图外壳**(`phoneFrame: false`),保留标准深色圆角边框。需要贴图壳时再传:
223
+
224
+ ```ts
225
+ createMiniAppSimulator({
226
+ mount,
227
+ phoneFrame: "iphone-14-pro-max", // 或 "nexus-5x"
228
+ });
229
+
230
+ // 运行时也可切换外壳(会重建渲染器并重新加载)
231
+ await simulator.switchPhoneFrame("iphone-14-pro-max");
232
+ ```
233
+
234
+ `phoneFrame: false`(无贴图外壳)时,模拟器内置创作发布同款标准设备边框:
235
+
236
+ - 颜色:`rgba(0, 0, 0, 0.65)`(`--color-text-paragraph`)
237
+ - 分档:与 ImagePreview / CoverPreview `@media` 一致(1439 / 1440 / 1680 / 1920 / 2285)
238
+ - 缩放:按逻辑 viewport 高度相对各档 `shellHeight`(442 / 552 / 662 / 729 / 868)等比放大
239
+
240
+ 窗口 resize 时自动重算分档。
241
+
242
+ ## stageFit(自适应缩放)
243
+
244
+ 默认开启:模拟器会按 mount 区域 **100% 撑满**自适应(`scaleMultiplier: 1`、`capToBounds: true`),父容器尺寸变化时通过 `ResizeObserver` 自动重算。
245
+
246
+ 手动拖拽缩放(playground 右上角手柄)需显式开启:
247
+
248
+ ```ts
249
+ createMiniAppSimulator({
250
+ mount,
251
+ stageFit: {
252
+ resizeHandle: true,
253
+ // 可选:与 playground 一致保持居中且不溢出
254
+ centerInBounds: true,
255
+ capToBounds: true,
256
+ },
257
+ });
258
+ ```
259
+
260
+ 传 `stageFit: false` 可关闭自适应,设备保持逻辑 viewport 原始像素尺寸。
@@ -0,0 +1,150 @@
1
+ # Simulator Server Playground 本地 HTTPS 与 AI 联调
2
+
3
+ 这份文档用于联调 `miniapp-simulator-server`、`miniapp-simulator` 和 AI SDK。推荐入口是:
4
+
5
+ ```text
6
+ https://local.devops.sit.xiaohongshu.com:5176/ai-chat
7
+ ```
8
+
9
+ 本地链路由两个进程组成:
10
+
11
+ ```text
12
+ 小应用 → xhs.AI → Bridge → Simulator 宿主 request
13
+ → 5176 HTTPS Playground / Vite proxy
14
+ ├─ /simulator-dev/*、/simulator/runtime-config → 127.0.0.1:5174
15
+ └─ Creator AI path → Creator beta 独立泳道
16
+ ```
17
+
18
+ `5174` 提供 Simulator Runtime,`5176` 承载 Playground、Service Worker 和本地代理。AI SDK 始终通过 Bridge 调用容器注入的宿主 `request` 能力;本地代理只是宿主 request 在开发环境中的网络实现,不是 SDK API,也没有 `/api/host-request` 业务协议层。
19
+
20
+ ## 1. 首次配置
21
+
22
+ 以下步骤从 `skill-designable-kit` 仓库根目录执行。
23
+
24
+ ### 1.1 配置本地域名
25
+
26
+ 在 `/etc/hosts` 增加:
27
+
28
+ ```text
29
+ 127.0.0.1 local.devops.sit.xiaohongshu.com
30
+ ```
31
+
32
+ macOS 可用下面的命令检查解析结果:
33
+
34
+ ```bash
35
+ dscacheutil -q host -a name local.devops.sit.xiaohongshu.com
36
+ ```
37
+
38
+ 结果应包含 `127.0.0.1`。修改 hosts 后无需把本地域名映射到真实的 beta 服务。
39
+
40
+ ### 1.2 启动 Simulator Server
41
+
42
+ 终端 A:
43
+
44
+ ```bash
45
+ pnpm --filter @skillbuilder/miniapp-simulator-server dev
46
+ ```
47
+
48
+ 该进程监听 `127.0.0.1:5174`,提供本地 Runtime、Service Worker 和运行时配置。已经构建过 Runtime、只改服务端代码时可以使用:
49
+
50
+ ```bash
51
+ pnpm --filter @skillbuilder/miniapp-simulator-server dev:fast
52
+ ```
53
+
54
+ ### 1.3 启动 HTTPS Playground
55
+
56
+ 终端 B:
57
+
58
+ ```bash
59
+ pnpm --filter @skillbuilder/miniapp-simulator-server-playground dev:https
60
+ ```
61
+
62
+ 首次启动会在 Playground 的 `node_modules/.vite/basic-ssl-devops/` 下生成本地证书。Vite 监听 `0.0.0.0:5176`,但浏览器应使用带证书域名的地址访问。
63
+
64
+ ### 1.4 信任本地证书
65
+
66
+ 终端 C,仅首次生成证书或证书重新生成后执行:
67
+
68
+ ```bash
69
+ pnpm --filter @skillbuilder/miniapp-simulator-server-playground trust:https
70
+ ```
71
+
72
+ 该脚本会在 macOS 登录钥匙串中信任证书,系统可能要求输入密码。执行后完全退出并重新打开 Chrome,再访问:
73
+
74
+ ```text
75
+ https://local.devops.sit.xiaohongshu.com:5176/ai-chat
76
+ ```
77
+
78
+ 只在 Chrome 证书警告页点击“继续访问”不够。页面虽然可能打开,但该 Origin 仍不满足 Service Worker 的可信上下文要求,Demo 会表现为 Runtime 脚本加载失败。
79
+
80
+ Windows/Linux 暂无自动信任脚本,需要从
81
+ `apps/miniapp-simulator-server-playground/node_modules/.vite/basic-ssl-devops/_cert.pem` 提取
82
+ `BEGIN CERTIFICATE` 到 `END CERTIFICATE` 段,再导入系统或浏览器信任库;不要导入同文件中的私钥段。
83
+
84
+ ## 2. 日常启动与自检
85
+
86
+ 日常只需启动两个进程:
87
+
88
+ ```bash
89
+ # 终端 A
90
+ pnpm --filter @skillbuilder/miniapp-simulator-server dev
91
+
92
+ # 终端 B
93
+ pnpm --filter @skillbuilder/miniapp-simulator-server-playground dev:https
94
+ ```
95
+
96
+ 打开 `/ai-chat` 后,Network 面板应看到以下请求成功:
97
+
98
+ | 请求 | 预期 | 作用 |
99
+ | --- | --- | --- |
100
+ | `GET /simulator-dev/runtime-manifest` | 200 | 薄 SDK 获取本地 Runtime 地址 |
101
+ | `GET /simulator-dev` | 200 | Runtime 容器页面 |
102
+ | `GET /simulator-dev/runtime.js` | 200 | 本地 Runtime 脚本 |
103
+ | `GET /simulator-dev/sw.js` | 200 | 宿主 request 使用的 Service Worker |
104
+ | `POST /simulator/runtime-config` | 200 | 容器运行时配置与宿主能力下发 |
105
+
106
+ 可用下面的命令先验证 5176 与证书服务是否可访问;`-k` 只用于命令行探活,不代表浏览器已信任证书:
107
+
108
+ ```bash
109
+ curl -kI https://local.devops.sit.xiaohongshu.com:5176/ai-chat
110
+ curl -kI https://local.devops.sit.xiaohongshu.com:5176/simulator-dev/runtime-manifest
111
+ ```
112
+
113
+ ## 3. Creator AI 独立泳道联调
114
+
115
+ 当前 `/ai-chat` 的 Creator AI 地址只用于本地开发,不进入 AI SDK 的线上默认配置。联调流程如下:
116
+
117
+ 1. 在已登录的 Creator beta 页面找到一条能够请求独立泳道的 AI 请求。
118
+ 2. 在 DevTools Network 中选择 **Copy as cURL**。
119
+ 3. 把完整 cURL 粘贴到 `/ai-chat` 的 “Creator cURL” 输入框并应用。
120
+ 4. 页面提取并展示 `Cookie`、`Origin`、`Referer`、`x-s`、`x-s-common`、`x-t` 和 `x-minitool-appid`。
121
+ 5. 触发非流式 Demo,确认请求体和 `x-minitool-appid` 仍由 AI SDK 生成,鉴权信息由 5176 的 Vite proxy 恢复到上游请求。
122
+
123
+ 页面会把 cURL 和 appId 存在当前浏览器的 `localStorage`,方便刷新后继续调试。它们是可见的开发凭据:不要提交到 Git、粘贴到公共文档或用于线上配置,调试完成后点击“清空鉴权”。
124
+
125
+ 签名和 Cookie 都可能过期。出现 `HTTP 401` 或“无登录信息”时,应从 Creator 页面重新复制一条刚刚成功的 cURL,不要只替换单个签名字段。
126
+
127
+ 本期 AI SDK 业务验收只覆盖非流式调用;流式 Case 仅用于观察 Simulator/Bridge 能力,不作为本期上线结论。
128
+
129
+ ## 4. 常见问题
130
+
131
+ | 现象 | 原因 | 处理方式 |
132
+ | --- | --- | --- |
133
+ | 页面显示“不安全”,Demo 全部加载失败 | 证书未被系统信任,Service Worker 无法注册 | 先运行 `dev:https` 生成证书,再运行 `trust:https`,完全重启 Chrome |
134
+ | 证书过期或域名不匹配 | 本地证书已过期,或证书不是为当前域名生成 | 停止 5176,删除 `node_modules/.vite/basic-ssl-devops/` 后重新运行 `dev:https` 和 `trust:https` |
135
+ | `An unknown error occurred when fetching the script` | 5174 未启动、Runtime 产物未构建,或 Service Worker 仍使用旧缓存 | 启动 5174;重启 5176;必要时在 Application → Service Workers 注销旧 Worker 并清除此站点数据 |
136
+ | `/simulator-dev/runtime.js` 或 `/sw.js` 返回 503 | 本地 Runtime 产物不存在 | 使用普通 `dev`/`dev:https` 先执行构建,不要直接使用跳过构建的启动方式 |
137
+ | `Blocked request. This host ... is not allowed` | 使用了旧 Vite 配置或改完配置后未重启 | 更新代码并重启 5176;当前 `allowedHosts` 已包含 `local.devops.sit.xiaohongshu.com` |
138
+ | AI 请求 401 / “无登录信息” | cURL 未带完整 Cookie,或 Cookie/签名已过期 | 回到已登录 Creator 页面重新 Copy as cURL 并完整粘贴 |
139
+ | `Origin-Agent-Cluster` 警告 | 同一 Origin 曾以不同响应头打开,浏览器保留了旧站点状态 | 关闭该 Origin 的所有标签页,清除站点数据后重新打开;该警告本身不等于 AI 请求失败 |
140
+ | `favicon.ico` 404 | Playground 没有 favicon | 可忽略,不影响 Runtime、Bridge 或 AI 请求 |
141
+ | Chrome 顶部提示使用不受支持的命令行标记 | 曾启用 `Insecure origins treated as secure` | 在 `chrome://flags/#unsafely-treat-insecure-origin-as-secure` 清空地址并选择 Disabled,然后重启;本链路使用真实 HTTPS 证书,不需要该 flag |
142
+
143
+ 如果页面能加载但按钮点击后像是刷新,先在 Network 面板确认是否重新请求了页面文档。独立 Case 中的按钮必须使用 `type="button"`,表单提交事件需要调用 `preventDefault()`;这类问题与 HTTPS 和 AI 鉴权无关。
144
+
145
+ ## 5. 边界说明
146
+
147
+ - `miniToolEnv.gateways` 由宿主按环境注入;AI SDK 不负责判断审核、创服或真机域名。
148
+ - 小应用只使用 `xhs.AI.createModel(...)` 和 AI SDK API,不读取 Cookie,也不感知 Vite proxy。
149
+ - Simulator 本地独立泳道通过粘贴 cURL 恢复 Creator 登录上下文,仅供开发。
150
+ - VibeCreator Builder 的正式链路应由网关校验 VibeCreator 扫码 session,并在服务端置换下游登录态;浏览器不持有下游 Cookie。
@@ -0,0 +1,275 @@
1
+ # 模拟器 Storage API 设计
2
+
3
+ ## 1. 背景与目标
4
+
5
+ 模拟器需要向小工具提供与客户端调用方式一致的异步 Storage API,使业务代码可以在本地、远程预览等场景完成缓存写入、读取、删除、容量查询和清空操作。
6
+
7
+ 本文描述当前实现的接口、数据模型、隔离规则、容量计算、并发语义和安全边界,作为联调、测试与后续演进的统一依据。
8
+
9
+ 设计目标:
10
+
11
+ - 对外暴露 `xhs.miniTool` 下的 5 个异步 Storage API;
12
+ - 默认实现由模拟器 Core 提供,本地和远程模式保持一致的存储行为;
13
+ - 数据在页面刷新后仍可读取,并按小工具维度隔离;
14
+ - 对单条数据和单个 scope 的总量进行容量控制;
15
+ - 宿主可按 API 名覆盖默认实现,以接入平台侧存储能力。
16
+
17
+ 非目标:
18
+
19
+ - 不提供同步 Storage API;
20
+ - 不提供 TTL、批量事务、变更订阅和跨设备同步;
21
+ - 不将 `encrypt` 实现为密码学加密;
22
+ - 不在默认 scope 中提供用户级隔离。
23
+
24
+ ## 2. 总体架构
25
+
26
+ ```text
27
+ 小工具业务代码
28
+ │ xhs.miniTool.setStorage / getStorage / ...
29
+ ▼
30
+ miniapp-js-api
31
+ │ Bridge invoke(event, options)
32
+ ▼
33
+ 模拟器 Bridge / Remote Host API Bridge
34
+ │ 按 event 分发;宿主同名 handler 可覆盖
35
+ ▼
36
+ createSimulatorStorageHostApi
37
+ │ 参数校验、JSON 复制、配额、写操作排队
38
+ ▼
39
+ SimulatorStorageRepository
40
+ ▼
41
+ 当前 Runtime origin 的 IndexedDB
42
+ ```
43
+
44
+ Storage 属于模拟器 Core 的默认 JSAPI。业务侧只依赖 `xhs.miniTool`,不直接访问 IndexedDB。这样可以统一接口校验、隔离 scope、容量规则和错误语义,同时保留宿主替换底层实现的能力。
45
+
46
+ 默认仓储使用 IndexedDB,而不使用 `localStorage`,原因是 Storage API 本身是异步接口,IndexedDB 不阻塞主线程,容量和数据组织能力也更适合 10 MiB 级别的数据。数据仍受浏览器 origin 存储边界和浏览器清理策略约束。
47
+
48
+ ## 3. API 设计
49
+
50
+ 所有接口都支持 Promise 调用方式以及 SDK 的 `success`、`fail`、`complete` 回调约定。本文示例使用 Promise。
51
+
52
+ | API | 入参 | 成功结果 | 行为 |
53
+ | --- | --- | --- | --- |
54
+ | `setStorage` | `key: string`、`data: unknown`、`encrypt?: boolean` | `{}` | 新增或覆盖当前 scope 下的 key |
55
+ | `getStorage` | `key: string`、`encrypt?: boolean` | `{ data: unknown }` | 读取当前 scope 下的 key;不存在时返回 `data: null` |
56
+ | `removeStorage` | `key: string` | `{}` | 删除当前 scope 下的 key;key 不存在也成功 |
57
+ | `getStorageInfo` | 无业务入参 | `{ keys, usedBytes, limitBytes }` | 返回排序后的 key 和容量信息,容量单位为字节 |
58
+ | `clearStorage` | 无业务入参 | `{}` | 只清空当前 scope 的全部 key |
59
+
60
+ ### 3.1 写入与读取示例
61
+
62
+ ```ts
63
+ await window.xhs.miniTool.setStorage({
64
+ key: "profile",
65
+ data: { nickname: "小红薯", tags: ["旅行", "摄影"] },
66
+ });
67
+
68
+ const { data } = await window.xhs.miniTool.getStorage({
69
+ key: "profile",
70
+ });
71
+ ```
72
+
73
+ ### 3.2 删除、查询容量与清空
74
+
75
+ ```ts
76
+ await window.xhs.miniTool.removeStorage({ key: "profile" });
77
+
78
+ const { keys, usedBytes, limitBytes } =
79
+ await window.xhs.miniTool.getStorageInfo();
80
+
81
+ await window.xhs.miniTool.clearStorage();
82
+ ```
83
+
84
+ ## 4. 数据模型与持久化
85
+
86
+ 默认实现使用如下 IndexedDB 结构:
87
+
88
+ | 配置项 | 值 |
89
+ | --- | --- |
90
+ | Database | `miniapp-simulator-storage` |
91
+ | Version | `1` |
92
+ | Object Store | `entries` |
93
+ | 主键 | `[scope, key]` 复合主键 |
94
+ | 索引 | `by_scope`,字段为 `scope`,非唯一 |
95
+
96
+ 每条记录结构如下:
97
+
98
+ ```ts
99
+ interface SimulatorStorageRecord {
100
+ scope: string;
101
+ key: string;
102
+ data: unknown;
103
+ sizeBytes: number;
104
+ encrypted: boolean;
105
+ }
106
+ ```
107
+
108
+ - `scope`:模拟器计算出的逻辑隔离标识;
109
+ - `key`:业务传入的非空字符串;
110
+ - `data`:经过 JSON 序列化再反序列化后的值;
111
+ - `sizeBytes`:序列化后数据的 UTF-8 字节数;
112
+ - `encrypted`:写入时的 `encrypt` 标记,仅用于读取一致性校验。
113
+
114
+ 数据库归属于当前 Runtime origin。浏览器刷新不会主动清空数据;清理站点数据、无痕会话结束、浏览器配额回收或 Runtime origin 变化都可能导致数据不可用。
115
+
116
+ ## 5. 隔离模型
117
+
118
+ ### 5.1 scope 生成规则
119
+
120
+ | 运行方式 | scope |
121
+ | --- | --- |
122
+ | 模拟器 Core 默认模式 | `JSON.stringify(["app", appId])` |
123
+ | 远程 Runtime 模式 | `JSON.stringify([businessLine, appId])` |
124
+
125
+ Core 模式未传有效 `appId` 时会生成当前挂载实例的随机 ID。这种情况下刷新或重新创建实例不能依赖原 scope 复用数据;需要稳定持久化时必须传入稳定的 `appId`。
126
+
127
+ 复合主键 `[scope, key]` 保证不同 scope 下可以存在同名 key,`getStorageInfo` 和 `clearStorage` 也只作用于当前 scope。
128
+
129
+ ### 5.2 用户隔离边界
130
+
131
+ 默认 scope 不包含用户 ID。同一 Runtime origin 下,只要 `businessLine` 和 `appId` 相同,不同登录账号会访问同一份模拟器缓存。因此不得使用默认 Storage 保存账号令牌、密码或其他需要按用户强隔离的敏感数据。
132
+
133
+ 需要用户级隔离时,接入方应选择以下方案之一:
134
+
135
+ - 在账号退出或切换时调用 `clearStorage`;
136
+ - 在业务 key 中加入经过规范化的用户维度;
137
+ - 通过宿主同名 Host API 覆盖默认实现,在宿主存储层实现用户级命名空间和生命周期管理。
138
+
139
+ ## 6. 序列化语义
140
+
141
+ `setStorage` 通过 `JSON.stringify(data)` 生成用于计费和保存的值,再通过 `JSON.parse` 生成 JSON 兼容副本。业务读取到的是 JSON 语义下的副本,不是写入对象的引用。
142
+
143
+ | 输入类型 | 结果 |
144
+ | --- | --- |
145
+ | string、number、boolean、null、普通对象、数组 | 按 JSON 语义保存 |
146
+ | `Date` | 保存为 ISO 字符串 |
147
+ | 对象属性中的 `undefined`、function、symbol | 该属性被忽略 |
148
+ | 数组元素中的 `undefined`、function、symbol | 转换为 `null` |
149
+ | `NaN`、`Infinity`、`-Infinity` | 转换为 `null` |
150
+ | 根值为 `undefined`、function 或 symbol | 拒绝写入,错误码 `10001` |
151
+ | `BigInt` 或循环引用 | 拒绝写入,错误码 `10001` |
152
+
153
+ `key` 当前只校验为非空字符串,不进行 trim、字符集限制或长度限制。调用方应使用稳定、可读且带业务前缀的 key,避免空白 key、超长 key 和非预期覆盖。
154
+
155
+ ## 7. 容量与计费
156
+
157
+ 容量按 `JSON.stringify(data)` 结果的 UTF-8 字节数计算,不包含 key、scope 和记录元数据。
158
+
159
+ | 限制 | 数值 | 失败错误码 |
160
+ | --- | --- | --- |
161
+ | 单条数据 | 1 MiB,即 `1 * 1024 * 1024` 字节 | `50201` |
162
+ | 单个 scope 总量 | 10 MiB,即 `10 * 1024 * 1024` 字节 | `50201` |
163
+
164
+ 覆盖已有 key 时,总量计算公式为:
165
+
166
+ ```text
167
+ 写入后总量 = 当前总量 - 旧值字节数 + 新值字节数
168
+ ```
169
+
170
+ 因此覆盖不会重复计算旧值。`getStorageInfo` 返回:
171
+
172
+ - `keys`:当前 scope 的全部 key,按字符串排序;
173
+ - `usedBytes`:当前 scope 已使用的字节数;
174
+ - `limitBytes`:固定为 `10485760`,即 10 MiB。
175
+
176
+ ## 8. 并发与一致性
177
+
178
+ 同一个 `createSimulatorStorageHostApi` 实例内,`setStorage`、`removeStorage` 和 `clearStorage` 进入同一条 mutation queue,按进入队列的顺序串行执行。某次写操作失败后,队列会继续处理后续操作,不会永久阻塞。
179
+
180
+ `getStorage` 和 `getStorageInfo` 不进入写队列,可以并发执行。业务代码如果需要“写后立即读”的确定性,必须等待写 Promise 完成:
181
+
182
+ ```ts
183
+ await window.xhs.miniTool.setStorage({ key: "draft", data: payload });
184
+ const { data } = await window.xhs.miniTool.getStorage({ key: "draft" });
185
+ ```
186
+
187
+ 以下情况不提供全局串行保证:
188
+
189
+ - 未等待 `setStorage` 完成就发起读取;
190
+ - 多个 Storage Host API 实例同时操作同一 scope;
191
+ - 多个页面或标签页共享同一 origin 和 scope。
192
+
193
+ 这些场景下 IndexedDB 负责事务原子性,但“先统计总量、再写入”的配额判断不是跨实例原子事务。接入方不应依赖并发写入时的严格全局顺序。
194
+
195
+ ## 9. `encrypt` 语义与安全边界
196
+
197
+ > `encrypt: true` 在模拟器默认实现中不执行密码学加密,也不会改变 IndexedDB 中 `data` 的明文表示。
198
+
199
+ 当前实现只把 `encrypt` 保存为布尔元数据。`getStorage` 必须传入与 `setStorage` 相同的值:
200
+
201
+ ```ts
202
+ await window.xhs.miniTool.setStorage({
203
+ key: "settings",
204
+ data: { theme: "dark" },
205
+ encrypt: true,
206
+ });
207
+
208
+ const { data } = await window.xhs.miniTool.getStorage({
209
+ key: "settings",
210
+ encrypt: true,
211
+ });
212
+ ```
213
+
214
+ 读写标记不一致时读取失败。该行为用于兼容客户端接口约定,不能作为数据保密能力。敏感数据应由宿主覆盖 Storage handler,并在可信边界内完成密钥管理、加解密和用户隔离;业务层也不应把凭证、私钥或长期令牌写入模拟器默认 Storage。
215
+
216
+ ## 10. 错误处理
217
+
218
+ | 场景 | 错误码 | 说明 |
219
+ | --- | --- | --- |
220
+ | key 缺失、为空或不是字符串 | `10001` | `INVALID_PARAM` |
221
+ | data 缺失、根值为 `undefined` 或不可 JSON 序列化 | `10001` | `INVALID_PARAM` |
222
+ | encrypt 不是 boolean | `10001` | `INVALID_PARAM` |
223
+ | 单条或 scope 总量超限 | `50201` | `STORAGE_QUOTA_EXCEEDED` |
224
+ | getStorage 的 encrypt 与写入标记不一致 | 暂无稳定错误码 | Promise reject,错误信息说明标记不一致 |
225
+ | IndexedDB 打开、阻塞或请求失败 | 暂无稳定错误码 | 底层错误经 Bridge 透传 |
226
+
227
+ 枚举中已定义 `50101 STORAGE_IO_ERROR`,默认 Storage 仓储当前未把 IndexedDB 异常统一归一化到该错误码。调用方应以 Promise reject 或 `fail` 回调作为失败判断,不应只依赖错误文案。
228
+
229
+ ## 11. 宿主覆盖机制
230
+
231
+ 模拟器先注册默认 Storage handlers,再合并宿主提供的同名 Host API。宿主实现可覆盖任意一个或全部 Storage API,用于接入客户端存储、用户隔离、加密、审计或其他平台能力。
232
+
233
+ 覆盖实现必须保持以下对外契约:
234
+
235
+ - API 名和异步调用方式不变;
236
+ - 参数校验、缺失 key、幂等删除等行为与本文一致;
237
+ - `getStorageInfo` 的容量单位为字节;
238
+ - 错误通过 reject 返回,并尽量使用统一错误码;
239
+ - 同一 scope 内的隔离和清理行为可预测。
240
+
241
+ 部分覆盖时需要特别谨慎。例如只覆盖 `setStorage` 而保留默认 `getStorage`,会造成读写落在不同存储后端。推荐成组覆盖 5 个接口。
242
+
243
+ ## 12. 生命周期与清理
244
+
245
+ - 模拟器实例销毁不会清空 IndexedDB;
246
+ - 页面刷新不会清空稳定 scope 的数据;
247
+ - `removeStorage` 只删除一个 `[scope, key]`;
248
+ - `clearStorage` 先按 `by_scope` 找到复合主键,再删除当前 scope 的全部记录;
249
+ - 其他 app、businessLine 或 scope 的数据不会被 `clearStorage` 删除;
250
+ - 浏览器站点数据清理和存储回收属于 Runtime 环境能力,模拟器不承诺永久持久化。
251
+
252
+ ## 13. 验收清单
253
+
254
+ - 5 个异步 API 均可通过 Promise 和回调调用;
255
+ - 写入后等待 Promise,再读取可得到 JSON 兼容副本;
256
+ - 相同 key 在不同 scope 下互不影响;
257
+ - 缺失 key 返回 `{ data: null }`;
258
+ - 删除不存在的 key 成功;
259
+ - 单条超过 1 MiB、总量超过 10 MiB 时返回 `50201`;
260
+ - 覆盖已有 key 时正确扣除旧值容量;
261
+ - `getStorageInfo.keys` 有序,容量单位和取整方式正确;
262
+ - 同实例写操作按调用顺序执行,失败后队列仍可继续;
263
+ - `clearStorage` 只清理当前 scope;
264
+ - `encrypt` 一致时可读,不一致时失败;
265
+ - 文档和用例明确 `encrypt` 不提供真实加密;
266
+ - 宿主覆盖时 5 个接口使用同一后端和隔离规则。
267
+
268
+ ## 14. 代码位置
269
+
270
+ - 默认实现:`packages/miniapp-simulator/src/runtime/storage-host-api.ts`
271
+ - Core 注册:`packages/miniapp-simulator/src/core/simulator.ts`
272
+ - 远程 Runtime 注册:`packages/miniapp-simulator/src/remote/runtime.ts`
273
+ - SDK API:`packages/miniapp-js-api/src/apis/storage/`(已随包外移)
274
+ - JSON Schema:`packages/miniapp-js-api/src/schemas/*Storage*.schema.json`(已外移)
275
+ - 测试:`packages/miniapp-simulator/tests/remote-storage.test.ts`