@skillbuilder/miniapp-simulator 0.0.0-stage → 0.2.0

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 (229) hide show
  1. package/README.md +263 -2
  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 +971 -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 +195 -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 +35 -0
  27. package/docs/remote-architecture.md +404 -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/docs/worker-runtime.md +62 -0
  36. package/lib/business-line-CMb3ehRn.mjs +15 -0
  37. package/lib/client-CO-UjIY3.mjs +775 -0
  38. package/lib/index-B5oixYXr.mjs +574 -0
  39. package/lib/index.js +10763 -0
  40. package/lib/preload/webview-preload.js +37 -0
  41. package/lib/remote-client.js +11 -0
  42. package/lib/types/business-line.d.ts +7 -0
  43. package/lib/types/capabilities/constants.d.ts +29 -0
  44. package/lib/types/capabilities/csp-snippet.d.ts +34 -0
  45. package/lib/types/capabilities/launch-options.d.ts +7 -0
  46. package/lib/types/capabilities/performance-policy.d.ts +5 -0
  47. package/lib/types/capabilities/security-head-snippet.d.ts +21 -0
  48. package/lib/types/capabilities/security.d.ts +25 -0
  49. package/lib/types/capabilities/style-snippet.d.ts +2 -0
  50. package/lib/types/core/controllers/game-playtest.d.ts +9 -0
  51. package/lib/types/core/controllers/gesture.d.ts +8 -0
  52. package/lib/types/core/controllers/host-api.d.ts +19 -0
  53. package/lib/types/core/controllers/index.d.ts +11 -0
  54. package/lib/types/core/controllers/inspector.d.ts +20 -0
  55. package/lib/types/core/controllers/note-preview.d.ts +9 -0
  56. package/lib/types/core/controllers/permission-sheet.d.ts +7 -0
  57. package/lib/types/core/controllers/widget-cover.d.ts +12 -0
  58. package/lib/types/core/lifecycle.d.ts +7 -0
  59. package/lib/types/core/options.d.ts +33 -0
  60. package/lib/types/core/simulator.d.ts +9 -0
  61. package/lib/types/errors.d.ts +11 -0
  62. package/lib/types/event-bus.d.ts +15 -0
  63. package/lib/types/gesture/host-bus.d.ts +113 -0
  64. package/lib/types/gesture/index.d.ts +27 -0
  65. package/lib/types/gesture/plugins/double-tap.d.ts +10 -0
  66. package/lib/types/gesture/plugins/index.d.ts +27 -0
  67. package/lib/types/gesture/plugins/long-press.d.ts +13 -0
  68. package/lib/types/gesture/plugins/multi-touch-hint.d.ts +11 -0
  69. package/lib/types/gesture/plugins/multi-touch-switch.d.ts +18 -0
  70. package/lib/types/gesture/plugins/selection-disabler.d.ts +11 -0
  71. package/lib/types/gesture/plugins/tap.d.ts +11 -0
  72. package/lib/types/gesture/plugins/touch-emitter.d.ts +11 -0
  73. package/lib/types/gesture/plugins/wheel-scroll.d.ts +11 -0
  74. package/lib/types/gesture/receiver-runtime.d.ts +10 -0
  75. package/lib/types/gesture/receiver-runtime.impl.d.ts +6 -0
  76. package/lib/types/gesture/touch-cursor.d.ts +78 -0
  77. package/lib/types/gesture/touch-dot.d.ts +12 -0
  78. package/lib/types/gesture/types.d.ts +206 -0
  79. package/lib/types/host/shadow-host.d.ts +40 -0
  80. package/lib/types/index.d.ts +56 -0
  81. package/lib/types/injection/guest-log-mark.d.ts +7 -0
  82. package/lib/types/injection/head-builder.d.ts +4 -0
  83. package/lib/types/injection/index.d.ts +19 -0
  84. package/lib/types/injection/modules/console-forward.d.ts +2 -0
  85. package/lib/types/injection/modules/gesture.d.ts +6 -0
  86. package/lib/types/injection/modules/inspector-guest-runtime.d.ts +5 -0
  87. package/lib/types/injection/modules/inspector-pause-runtime.d.ts +6 -0
  88. package/lib/types/injection/modules/inspector.d.ts +8 -0
  89. package/lib/types/injection/modules/jsapi-channel.d.ts +9 -0
  90. package/lib/types/injection/modules/jsapi.d.ts +22 -0
  91. package/lib/types/injection/modules/render-done.d.ts +2 -0
  92. package/lib/types/injection/modules/resource-guard.d.ts +7 -0
  93. package/lib/types/injection/modules/scroll-containment.d.ts +3 -0
  94. package/lib/types/injection/modules/security.d.ts +3 -0
  95. package/lib/types/injection/modules/widget-snapshot.d.ts +13 -0
  96. package/lib/types/injection/preload-builder.d.ts +41 -0
  97. package/lib/types/injection/registry.d.ts +20 -0
  98. package/lib/types/injection/types.d.ts +45 -0
  99. package/lib/types/inspector/host-inspector.d.ts +108 -0
  100. package/lib/types/inspector/index.d.ts +8 -0
  101. package/lib/types/inspector/selector.d.ts +18 -0
  102. package/lib/types/native-ui/devtools/CapabilitiesView.d.ts +18 -0
  103. package/lib/types/native-ui/devtools/DevtoolsPanel.d.ts +24 -0
  104. package/lib/types/native-ui/devtools/LogsView.d.ts +17 -0
  105. package/lib/types/native-ui/devtools/dom.d.ts +40 -0
  106. package/lib/types/native-ui/devtools/index.d.ts +19 -0
  107. package/lib/types/native-ui/devtools/log-entry.d.ts +8 -0
  108. package/lib/types/native-ui/devtools/shortcut.d.ts +12 -0
  109. package/lib/types/native-ui/devtools/tokens.d.ts +14 -0
  110. package/lib/types/native-ui/devtools/types.d.ts +51 -0
  111. package/lib/types/native-ui/index.d.ts +17 -0
  112. package/lib/types/native-ui/note-publish/defaults.d.ts +40 -0
  113. package/lib/types/native-ui/note-publish/index.d.ts +2 -0
  114. package/lib/types/native-ui/note-publish/miniapp-note-publish.d.ts +15 -0
  115. package/lib/types/native-ui/note-publish/note-publish-host.d.ts +15 -0
  116. package/lib/types/native-ui/note-publish/view-data.d.ts +21 -0
  117. package/lib/types/native-ui/permission-sheet/defaults.d.ts +12 -0
  118. package/lib/types/native-ui/permission-sheet/index.d.ts +4 -0
  119. package/lib/types/native-ui/permission-sheet/miniapp-permission-sheet.d.ts +19 -0
  120. package/lib/types/native-ui/permission-sheet/permission-sheet-host.d.ts +23 -0
  121. package/lib/types/native-ui/permission-sheet/types.d.ts +8 -0
  122. package/lib/types/native-ui/rednote-preview/chrome-tone.d.ts +44 -0
  123. package/lib/types/native-ui/rednote-preview/defaults.d.ts +30 -0
  124. package/lib/types/native-ui/rednote-preview/half-sheet-drag.d.ts +61 -0
  125. package/lib/types/native-ui/rednote-preview/index.d.ts +2 -0
  126. package/lib/types/native-ui/rednote-preview/register.d.ts +7 -0
  127. package/lib/types/native-ui/rednote-preview/styles.d.ts +4 -0
  128. package/lib/types/native-ui/rednote-preview/xhs-note-detail-shell.d.ts +70 -0
  129. package/lib/types/native-ui/rednote-preview/xhs-note-half-sheet.d.ts +100 -0
  130. package/lib/types/native-ui/rednote-preview/xhs-profile-shell.d.ts +31 -0
  131. package/lib/types/native-ui/toast/frame-toast-host.d.ts +18 -0
  132. package/lib/types/native-ui/toast/index.d.ts +19 -0
  133. package/lib/types/presets.d.ts +11 -0
  134. package/lib/types/remote/client.d.ts +89 -0
  135. package/lib/types/remote/jsapi-config.d.ts +9 -0
  136. package/lib/types/remote/load-timing.d.ts +46 -0
  137. package/lib/types/remote/protocol.d.ts +165 -0
  138. package/lib/types/remote/review-launch.d.ts +6 -0
  139. package/lib/types/remote/runtime-entry.d.ts +1 -0
  140. package/lib/types/remote/runtime.d.ts +14 -0
  141. package/lib/types/remote/simulator-prompt.d.ts +9 -0
  142. package/lib/types/remote/widget-snapshot-guest-runtime.d.ts +1 -0
  143. package/lib/types/remote-client.d.ts +5 -0
  144. package/lib/types/renderer/base.d.ts +8 -0
  145. package/lib/types/renderer/content-host/iframe-content-host.d.ts +84 -0
  146. package/lib/types/renderer/content-host/index.d.ts +3 -0
  147. package/lib/types/renderer/content-host/types.d.ts +89 -0
  148. package/lib/types/renderer/content-host/webview-content-host.d.ts +53 -0
  149. package/lib/types/renderer/iframe.d.ts +93 -0
  150. package/lib/types/renderer/index.d.ts +2 -0
  151. package/lib/types/renderer/rednote-shell.d.ts +73 -0
  152. package/lib/types/renderer/webview.d.ts +59 -0
  153. package/lib/types/runtime/context.d.ts +31 -0
  154. package/lib/types/runtime/jsapi-modules/context.d.ts +19 -0
  155. package/lib/types/runtime/jsapi-modules/filesystem.d.ts +19 -0
  156. package/lib/types/runtime/jsapi-modules/index.d.ts +9 -0
  157. package/lib/types/runtime/jsapi-modules/interaction.d.ts +4 -0
  158. package/lib/types/runtime/jsapi-modules/location.d.ts +3 -0
  159. package/lib/types/runtime/jsapi-modules/media.d.ts +13 -0
  160. package/lib/types/runtime/jsapi-modules/network.d.ts +3 -0
  161. package/lib/types/runtime/jsapi-modules/storage.d.ts +26 -0
  162. package/lib/types/runtime/jsapi-modules/system.d.ts +4 -0
  163. package/lib/types/runtime/jsapi-modules/types.d.ts +21 -0
  164. package/lib/types/runtime/jsapi-modules/window-info.d.ts +43 -0
  165. package/lib/types/runtime/request/host-request.d.ts +13 -0
  166. package/lib/types/runtime/request/request-manager-frame.d.ts +29 -0
  167. package/lib/types/runtime/request/request-runtime.d.ts +26 -0
  168. package/lib/types/runtime/request/request-types.d.ts +26 -0
  169. package/lib/types/runtime/request/sse-text-framer.d.ts +16 -0
  170. package/lib/types/seal-config.d.ts +134 -0
  171. package/lib/types/sw/index.d.ts +9 -0
  172. package/lib/types/sw/parse-raw.d.ts +34 -0
  173. package/lib/types/sw/protocol.d.ts +19 -0
  174. package/lib/types/sw/served-entry.d.ts +9 -0
  175. package/lib/types/sw/service-worker-content-host.d.ts +11 -0
  176. package/lib/types/sw/service-worker-host.d.ts +74 -0
  177. package/lib/types/sw/service-worker-renderer.d.ts +10 -0
  178. package/lib/types/sw/worker/fs-codec.d.ts +23 -0
  179. package/lib/types/sw/worker/fs-ops.d.ts +22 -0
  180. package/lib/types/sw/worker/fs-store.d.ts +29 -0
  181. package/lib/types/sw/worker/globals.d.ts +50 -0
  182. package/lib/types/sw/worker/main.d.ts +6 -0
  183. package/lib/types/sw/worker/mime.d.ts +2 -0
  184. package/lib/types/sw/worker/mount-store.d.ts +48 -0
  185. package/lib/types/sw/worker/paths.d.ts +4 -0
  186. package/lib/types/sw/worker/serve.d.ts +13 -0
  187. package/lib/types/tracker/constants.d.ts +14 -0
  188. package/lib/types/tracker/default-tracker.d.ts +19 -0
  189. package/lib/types/tracker/index.d.ts +70 -0
  190. package/lib/types/transport/bridge-protocol.d.ts +78 -0
  191. package/lib/types/transport/iframe-transport.d.ts +39 -0
  192. package/lib/types/transport/index.d.ts +14 -0
  193. package/lib/types/transport/jsapi-bridge-messages.d.ts +36 -0
  194. package/lib/types/transport/types.d.ts +48 -0
  195. package/lib/types/transport/webview-transport.d.ts +52 -0
  196. package/lib/types/types/events.d.ts +130 -0
  197. package/lib/types/types/index.d.ts +11 -0
  198. package/lib/types/types/inspector.d.ts +172 -0
  199. package/lib/types/types/jsapi.d.ts +18 -0
  200. package/lib/types/types/options.d.ts +851 -0
  201. package/lib/types/types/renderer.d.ts +145 -0
  202. package/lib/types/utils/ancestor-scroll.d.ts +11 -0
  203. package/lib/types/utils/error-svg.d.ts +12 -0
  204. package/lib/types/utils/frameless-shell.d.ts +114 -0
  205. package/lib/types/utils/miniapp-chrome-svg.d.ts +47 -0
  206. package/lib/types/utils/package-constants.d.ts +8 -0
  207. package/lib/types/utils/screenshot.d.ts +14 -0
  208. package/lib/types/utils/simulator-pc-guard.d.ts +10 -0
  209. package/lib/types/utils/stage-fit.d.ts +6 -0
  210. package/lib/types/utils/touch-emulate.d.ts +26 -0
  211. package/lib/types/utils/zip-entry.d.ts +13 -0
  212. package/lib/types/workbench/index.d.ts +63 -0
  213. package/lib/types/workbench/preview-transition.d.ts +8 -0
  214. package/lib/types/workbench/server/artifact.d.ts +8 -0
  215. package/lib/types/workbench/server/index.d.ts +19 -0
  216. package/lib/types/workbench/server/watch.d.ts +6 -0
  217. package/lib/types/workbench/view.d.ts +36 -0
  218. package/lib/types/worker/adapters/csp-policy.d.ts +7 -0
  219. package/lib/types/worker/adapters/host-lifecycle.d.ts +5 -0
  220. package/lib/types/worker/adapters/mount-recovery.d.ts +2 -0
  221. package/lib/types/worker/adapters/package-policy.d.ts +12 -0
  222. package/lib/types/worker/adapters/page-lifecycle.d.ts +1 -0
  223. package/lib/types/worker/adapters/protocol.d.ts +49 -0
  224. package/lib/types/worker/adapters/scripts.d.ts +2 -0
  225. package/lib/types/worker/adapters/service-worker.d.ts +2 -0
  226. package/lib/workbench-server.mjs +183 -0
  227. package/lib/workbench.js +1404 -0
  228. package/package.json +61 -4
  229. package/scripts/mock-cli-dev.mjs +52 -0
@@ -0,0 +1 @@
1
+ const h=self,pe="index.html",me="default-src 'none'; script-src 'none'; connect-src 'none'; worker-src 'none'; child-src 'none'",ge={workerCsp:me},G=ge.workerCsp,ye=["default-src","script-src","connect-src","worker-src","child-src"];function Z(e){if(!e||"object"!=typeof e||Array.isArray(e))return;const t={};for(const[n,r]of Object.entries(e)){if(!ye.includes(n)||"string"!=typeof r||!r.trim()||/[;,]|[^\x20-\x7e]/u.test(r))return;t[n]=r.trim()}return Object.keys(t).length?t:void 0}function we(e){const t=Z(e);return t?G.split("; ").map(e=>{const n=e.slice(0,e.indexOf(" "));return void 0===t[n]?e:`${n} ${t[n]}`}).join("; "):G}const be={html:"text/html; charset=utf-8",htm:"text/html; charset=utf-8",js:"text/javascript; charset=utf-8",mjs:"text/javascript; charset=utf-8",cjs:"text/javascript; charset=utf-8",css:"text/css; charset=utf-8",json:"application/json; charset=utf-8",xml:"application/xml; charset=utf-8",txt:"text/plain; charset=utf-8",csv:"text/csv; charset=utf-8",svg:"image/svg+xml",wasm:"application/wasm",map:"application/json; charset=utf-8"},_e={png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp",avif:"image/avif",ico:"image/x-icon",bmp:"image/bmp",woff:"font/woff",woff2:"font/woff2",ttf:"font/ttf",otf:"font/otf",eot:"application/vnd.ms-fontobject",mp4:"video/mp4",webm:"video/webm",ogg:"video/ogg",mp3:"audio/mpeg",wav:"audio/wav",m4a:"audio/mp4"};function Re(e){const t=e.split(".").pop()?.toLowerCase()??"";return be[t]??_e[t]??"application/octet-stream"}function b(e){try{return decodeURIComponent(e)}catch{return null}}function W(e){const t=[];for(const n of e.replace(/\\/g,"/").split("/"))if(""!==n&&"."!==n){if(".."===n)return"";t.push(n)}return t.join("/")}const g=new Map,Y=16;function ve(e){if(!g.has(e.id)&&g.size>=Y){const e=new Error("too many active simulator mounts (max 16)");throw e.code="ERR_MOUNT_LIMIT",e}const t=new Map;for(const[n,r]of Object.entries(e.files)){const e=W(n);e&&t.set(e,{bytes:r,mime:Re(e)})}let n;if(e.worker){if(e.remoteBase||e.worker.sessionId!==e.id)throw new TypeError("Invalid package Worker mount policy");n={sessionId:e.id,csp:Z(e.worker.csp)}}return g.set(e.id,{worker:n,workerResponses:new Map,files:t,injectHead:e.injectHead,csp:e.csp,remoteBase:e.remoteBase??"",fsScope:e.fsScope??"",ownerClientId:e.ownerClientId??""}),t.size}function xe(e){g.delete(e)}function Ee(e){let t=0;for(const[n,r]of g)!r.ownerClientId||e.has(r.ownerClientId)||(g.delete(n),t+=1);return t}function P(e){let t;try{t=new URL(e).pathname}catch{return null}for(const[e,n]of g){const r=`/${e}/`,i=t.indexOf(r);if(-1===i)continue;let o=b(t.slice(i+r.length));if(null===o)return null;(""===o||o.endsWith("/"))&&(o+=pe);const s=W(o);return s?{app:n,filePath:s}:null}return null}function Q(e){let t;try{t=new URL(e).pathname}catch{return null}for(const[e,n]of g)if(t.includes(`/${e}/`))return n;return null}const Se=5,Pe="miniapp:restore-worker-mount",De=["fsWriteTempFile","fsSaveFile","fsWriteFile","fsAppendFile","fsReadFile","fsReadDir","fsStatFile","fsUnlink","fsMkdir","fsGetStorageInfo"],F=new Map;async function Ie(e,t){const n=new URL(h.registration.scope),r=new URL(e.url);if(r.origin!==n.origin||!r.pathname.startsWith(n.pathname))return;const i=r.pathname.slice(n.pathname.length),o=i.indexOf("/");if(o<1)return;const s=i.slice(0,o);if(!/^[a-zA-Z0-9_-]{1,128}$/.test(s))return;const a=await h.clients.get(t);if(!a)return;const c=new URL(a.url);if(c.origin!==n.origin||!c.pathname.startsWith(`${n.pathname}${s}/`))return;let l=F.get(s);l||(l=Te(s,n.origin).finally(()=>F.delete(s)),F.set(s,l)),await l}async function Te(e,t){const n=(await h.clients.matchAll({type:"window",includeUncontrolled:!0})).filter(e=>new URL(e.url).origin===t);n.length&&await new Promise(t=>{const r=[];let i=n.length,o=!1;const s=()=>{if(!o){o=!0,clearTimeout(a);for(const e of r)e.close();t()}},a=setTimeout(s,5e3);for(const t of n){const n=new MessageChannel;r.push(n.port1),n.port1.onmessage=({data:e})=>{(!0===e?.ok||0===--i)&&s()};try{t.postMessage({type:Pe,id:e},[n.port2])}catch{n.port2.close(),0===--i&&s()}}})}async function ke(e,t){const n=()=>new Response("Worker entry denied",{status:403,headers:{"Content-Type":"text/plain; charset=utf-8","Cache-Control":"no-store"}});if("GET"!==e.method||"worker"!==e.destination||!t)return n();let r=P(e.url);if(!r)try{await Ie(e,t),r=P(e.url)}catch{return n()}if(!r?.app.worker)return n();const{app:i,filePath:o}=r,s=i.worker,a=new URL(h.registration.scope),c=new URL(e.url);if(c.origin!==a.origin||!c.pathname.startsWith(`${a.pathname}${s.sessionId}/`))return n();try{const r=await h.clients.get(t);if(!r||Q(r.url)!==i||P(e.url)?.app!==i)return n();const a=i.workerResponses.get(o);if(a)return a.clone();const c=i.files.get(o);if(!c)return n();const l=new Response(c.bytes,{headers:{"Content-Type":"text/javascript; charset=utf-8","Cache-Control":"no-store","X-Content-Type-Options":"nosniff","Content-Security-Policy":we(s.csp)}});return i.workerResponses.set(o,l),l.clone()}catch{return n()}}const Ce={"image/png":"png","image/jpeg":"jpg","image/webp":"webp","image/gif":"gif","video/mp4":"mp4","video/quicktime":"mov","video/webm":"webm"};function J(e){const t=new Uint8Array(e);return U(t,[137,80,78,71,13,10,26,10])?"image/png":U(t,[255,216,255])?"image/jpeg":x(t,0,"GIF87a")||x(t,0,"GIF89a")?"image/gif":x(t,0,"RIFF")&&x(t,8,"WEBP")?"image/webp":x(t,4,"ftyp")?"qt "===ee(t,8,12)?"video/quicktime":"video/mp4":U(t,[26,69,223,163])?"video/webm":void 0}function Me(e){if("string"!=typeof e||0===e.length)return{error:"data is required",code:"ERR_INVALID_ARGS"};const t=/^data:([^;,]+);base64,(.*)$/s.exec(e);if(!t)return{error:"only base64 data: URI is accepted",code:"ERR_INVALID_ARGS"};const n=t[1].toLowerCase(),r=Ce[n];if(!r)return{error:`unsupported mime "${n}"`,code:"ERR_UNSUPPORTED_MIME"};let i;try{i=atob(t[2])}catch{return{error:"invalid base64 data",code:"ERR_INVALID_ARGS"}}const o=new Uint8Array(i.length);for(let e=0;e<i.length;e++)o[e]=i.charCodeAt(e);return{bytes:o.buffer,mime:n,ext:r}}function U(e,t){return t.every((t,n)=>e[n]===t)}function x(e,t,n){return ee(e,t,t+n.length)===n}function ee(e,t,n){if(e.length<n)return"";let r="";for(let i=t;i<n;i++)r+=String.fromCharCode(e[i]);return r}const Fe="miniapp-simulator-fs",Ue=1,p="files";let _=null;function R(){return _||(_=new Promise((e,t)=>{const n=indexedDB.open(Fe,1);n.onupgradeneeded=()=>{const e=n.result;if(!e.objectStoreNames.contains(p)){const t=e.createObjectStore(p,{keyPath:["scope","path"]});t.createIndex("by_scope","scope",{unique:!1}),t.createIndex("by_createdAt","createdAt",{unique:!1})}},n.onsuccess=()=>e(n.result),n.onerror=()=>{_=null,t(n.error??new Error("indexedDB open failed"))},n.onblocked=()=>{_=null,t(new Error("indexedDB open blocked"))}}),_)}function te(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error??new Error("indexedDB request failed"))})}function T(e){return new Promise((t,n)=>{e.oncomplete=()=>t(),e.onerror=()=>n(e.error??new Error("indexedDB transaction failed")),e.onabort=()=>n(e.error??new Error("indexedDB transaction aborted"))})}function N(e,t,n,r,i){const o=Date.now();return{scope:e,path:t,mime:r,bytes:new Blob([n],{type:r}),isDir:i,createdAt:o,lastModified:o}}async function ne(e,t,n,r="application/octet-stream"){const i=(await R()).transaction(p,"readwrite");i.objectStore(p).put(N(e,t,n,r,!1)),await T(i)}async function A(e,t){const n=(await R()).transaction(p,"readwrite");n.objectStore(p).put(N(e,t,new ArrayBuffer(0),"application/x-directory",!0)),await T(n)}async function S(e,t){const n=(await R()).transaction(p,"readonly").objectStore(p),r=await te(n.get([e,t]));return r?{path:r.path,bytes:await r.bytes.arrayBuffer(),mime:r.mime,isDir:!0===r.isDir,lastModified:r.lastModified??r.createdAt??0}:null}async function v(e){const t=await R();return(await te(t.transaction(p,"readonly").objectStore(p).index("by_scope").getAll(e))).map(e=>({path:e.path,mime:e.mime,size:!0===e.isDir?0:e.bytes.size,isDir:!0===e.isDir,lastModified:e.lastModified??e.createdAt??0}))}async function Ae(e,t){const n=(await R()).transaction(p,"readwrite");n.objectStore(p).delete([e,t]),await T(n)}async function Le(e,t,n,r){const i=(await R()).transaction(p,"readwrite"),o=i.objectStore(p);o.put(N(e,n,r.bytes,r.mime,!1)),o.delete([e,t]),await T(i)}function Oe(e,t,n){if(e.files.has(t))return L(e,t);if(e.remoteBase){const r=new URL(t,e.remoteBase);return r.search=new URL(n.url).search,ie(r.href)}return L(e,t)}async function Be(e,t){try{const n=await h.clients.get(e);if(n){const e=Q(n.url);if(e){const n=new URL(t.url),r=b(n.pathname),i=null===r?"":W(r);if(i&&e.files.has(i))return L(e,i);if(e.remoteBase){return ie(new URL(n.pathname+n.search,new URL(e.remoteBase).origin).href)}}}}catch{}return fetch(t)}function re(e,t){return new Response(e,{status:200,headers:{"Content-Type":t,"Cache-Control":"no-store, no-cache, must-revalidate, max-age=0",Expires:"0",Pragma:"no-cache","Content-Length":String(e.byteLength)}})}function L(e,t){const n=e.files.get(t);if(!n)return new Response(`Not Found: ${t}`,{status:404,headers:{"Content-Type":"text/plain; charset=utf-8"}});if(n.mime.startsWith("text/html")){const t=We(new TextDecoder("utf-8").decode(n.bytes),e.injectHead),r={"Content-Type":"text/html; charset=utf-8","Cache-Control":"no-store","Origin-Agent-Cluster":"?1"};return e.csp&&(r["Content-Security-Policy"]=e.csp),new Response(t,{status:200,headers:r})}return re(n.bytes,n.mime)}async function ie(e){try{return await fetch(e,{method:"GET",credentials:"omit"})}catch{return new Response(`Bad Gateway: ${e}`,{status:502,headers:{"Content-Type":"text/plain; charset=utf-8"}})}}function We(e,t){if(!t)return e;const n=/<head\b[^>]*>/i.exec(e);if(n){const r=n.index+n[0].length;return e.slice(0,r)+t+e.slice(r)}const r=/<html\b[^>]*>/i.exec(e);if(r){const n=r.index+r[0].length;return`${e.slice(0,n)}<head>${t}</head>${e.slice(n)}`}return`<head>${t}</head>${e}`}const se=52428800,D=2097152,O=2097152,Ne=209715200,je=4,$e=255,K=1e3,j=new Set(["tmp","usr"]),k=new Set(["usr"]),qe=/^[A-Za-z0-9._-]{1,64}$/,I=new Map;let H=Promise.resolve();function C(e){const t=H.then(e,e);return H=t.then(()=>{},()=>{}),t}function ze(e){I.delete(e)}async function Ge(e){const t=t=>({type:"FS_ACK",op:e.op,id:e.id,reqId:e.reqId,...t});if(!g.has(e.id))return t({ok:!1,error:"simulator not ready",code:"ERR_NOT_MOUNTED"});try{return t(await Ye(e))}catch{return t({ok:!1,code:50101,error:"storage io error"})}}async function Ye(e){const t=e.fsScope||e.id;switch(e.op){case"fsWriteTempFile":return Ke(t,e);case"fsSaveFile":return He(t,e);case"fsWriteFile":return V(t,e,!1);case"fsAppendFile":return V(t,e,!0);case"fsReadFile":return Ve(t,e);case"fsReadDir":return Xe(t,e);case"fsStatFile":return Ze(t,e);case"fsUnlink":return Qe(t,e);case"fsMkdir":return Je(t,e);case"fsGetStorageInfo":return et(t);default:return{ok:!1,error:`unsupported operation: ${e.op}`,code:"ERR_UNSUPPORTED_OP"}}}async function Ke(e,t){const n=Me(t.data);if("error"in n)return{ok:!1,error:n.error,code:n.code};const r=`tmp/${de()}.${n.ext}`;return await ne(e,r,n.bytes,n.mime),{ok:!0,result:{path:r,url:he(e,r)}}}async function He(e,t){const n=m(t.tempFilePath,"tempFilePath");if(n)return n;if(void 0!==t.filePath&&null!==t.filePath){const e=m(t.filePath,"filePath");if(e)return e}const r=w(t.tempFilePath,e,new Set(["tmp"]),!1);if("code"in r)return l(r);const i=void 0===t.filePath||null===t.filePath?it(r.path):w(t.filePath,e,k,!1);return"code"in i?l(i):C(async()=>{const n=await S(e,r.path);if(!n)return l({code:50202,message:"file not found; reason=not_found"});if(n.isDir)return l({code:50205,message:"not a file"});const o=await v(e),s=q(o);if(s.has(i.path))return l({code:50204,message:"file already exists"});const a=ue(i.path,s);if("code"in a)return l(a);const c=ae(t.id,o,n.bytes.byteLength,0);return c?l(c):(await le(e,a.missing),await Le(e,r.path,i.path,n),ce(t.id,n.bytes.byteLength),{ok:!0,result:{savedFilePath:he(e,i.path)}})})}async function V(e,t,n){const r=m(t.filePath,"filePath");if(r)return r;const i=oe(t.data,"data",!0);if(i)return i;const o=m(t.encoding,"encoding");if(o)return o;const s=w(t.filePath,e,k,!1);if("code"in s)return l(s);if("base64"!==t.encoding&&"utf8"!==t.encoding)return l({code:10001,message:"invalid parameter; reason=encoding_unsupported"});const a=nt(t.data,t.encoding);return"code"in a?l(a):a.bytes.byteLength>D?l({code:50201,message:"storage quota exceeded; reason=single_over_limit"}):C(async()=>{const r=await v(e),i=q(r),o=await S(e,s.path);if(o?.isDir)return l({code:50205,message:"not a file"});const c=ue(s.path,i);if("code"in c)return l(c);const u=n?0:o?.bytes.byteLength??0,f=ae(t.id,r,a.bytes.byteLength,u);if(f)return l(f);await le(e,c.missing);let d=a.bytes;return n&&o&&(d=st(o.bytes,a.bytes)),await ne(e,s.path,d,at(d,t.encoding)),ce(t.id,a.bytes.byteLength),{ok:!0,result:{writtenBytes:a.bytes.byteLength}}})}async function Ve(e,t){const n=m(t.filePath,"filePath");if(n)return n;const r=m(t.encoding,"encoding");if(r)return r;const i=X(t.position,"position");if(i)return i;const o=X(t.length,"length");if(o)return o;const s=w(t.filePath,e,j,!1);if("code"in s)return l(s);if("base64"!==t.encoding&&"utf8"!==t.encoding)return l({code:10001,message:"invalid parameter; reason=encoding_unsupported"});const a=void 0===t.position?0:t.position,c=void 0===t.length?O:t.length;if(!Number.isSafeInteger(a)||a<0||!Number.isSafeInteger(c)||c<0||c>O)return l({code:10001,message:"invalid parameter; reason=range_invalid"});const u=await S(e,s.path);if(!u)return l({code:50202,message:"file not found"});if(u.isDir)return l({code:50205,message:"not a file"});const f=Math.min(u.bytes.byteLength,a+c),d=a>=u.bytes.byteLength?new ArrayBuffer(0):u.bytes.slice(a,f);let p;if("base64"===t.encoding)p=ot(d);else try{p=new TextDecoder("utf-8",{fatal:!0}).decode(d)}catch{return l({code:10001,message:"invalid parameter; reason=decode_failed"})}return{ok:!0,result:{data:p,bytesRead:d.byteLength,eof:a+d.byteLength>=u.bytes.byteLength}}}async function Xe(e,t){const n=m(t.dirPath,"dirPath");if(n)return n;const r=w(t.dirPath,e,j,!0);if("code"in r)return l(r);const i=await v(e);if(r.relativePath){const e=i.find(e=>e.path===r.path);if(!e)return l({code:50202,message:"file not found"});if(!e.isDir)return l({code:50206,message:"not a directory"})}const o=`${r.path}/`,s=i.filter(e=>{if(!e.path.startsWith(o))return!1;const t=e.path.slice(o.length);return t.length>0&&!t.includes("/")&&!E(e.path)}).sort((e,t)=>e.path.localeCompare(t.path));return{ok:!0,result:{files:s.slice(0,K).map(e=>({name:e.path.slice(o.length),size:e.isDir?0:e.size,lastModified:e.lastModified,isDir:e.isDir})),truncated:s.length>K}}}async function Ze(e,t){const n=m(t.filePath,"filePath");if(n)return n;const r=w(t.filePath,e,j,!1);if("code"in r)return l(r);const i=(await v(e)).find(e=>e.path===r.path);return i?{ok:!0,result:{size:i.isDir?0:i.size,lastModified:i.lastModified,isDir:i.isDir}}:l({code:50202,message:"file not found"})}async function Qe(e,t){const n=m(t.filePath,"filePath");if(n)return n;const r=w(t.filePath,e,k,!1);return"code"in r?l(r):C(async()=>{const t=await S(e,r.path);return t?t.isDir?l({code:50205,message:"not a file"}):(await Ae(e,r.path),{ok:!0,result:{}}):l({code:50202,message:"file not found"})})}async function Je(e,t){const n=m(t.dirPath,"dirPath");if(n)return n;if(void 0!==t.recursive&&"boolean"!=typeof t.recursive)return $("recursive");const r=w(t.dirPath,e,k,!1);if("code"in r)return l(r);const i=!0===t.recursive;return C(async()=>{const t=q(await v(e)),n=t.get(r.path);if(n)return n.isDir?i?{ok:!0,result:{}}:l({code:50204,message:"file already exists"}):l({code:50205,message:"not a file"});const o=fe(r.path);for(const e of o){const n=t.get(e);if(n&&!n.isDir)return l({code:50205,message:"not a file"});if(!n&&!i)return l({code:50202,message:"file not found"})}if(i)for(const n of o)t.has(n)||await A(e,n);return await A(e,r.path),{ok:!0,result:{}}})}async function et(e){const t=await v(e),n=t.filter(e=>!e.isDir&&e.path.startsWith("usr/")&&!E(e.path)),r=t.filter(e=>!e.isDir&&e.path.startsWith("tmp/")&&!E(e.path));return{ok:!0,result:{usedBytes:B(n),limitBytes:se,fileCount:n.length,tmpUsedBytes:B(r),writeChunkMaxBytes:D,readChunkMaxBytes:O}}}function m(e,t){return oe(e,t,!1)}function oe(e,t,n){if("string"!=typeof e||!(n||e.length>0))return $(t)}function X(e,t){if(void 0!==e&&"number"!=typeof e)return $(t)}function $(e){return{ok:!1,code:10001,error:`invalid parameter; param=${e}`}}function l(e){return{ok:!1,code:e.code,error:e.message}}function w(e,t,n,r){const i=tt(e,t);if(null===i)return{code:50203,message:"invalid path; reason=dir_not_allowed"};if("__DECODE_ERROR__"===i)return{code:50203,message:"invalid path; reason=traversal"};const o=i.indexOf("/"),s=(-1===o?i:i.slice(0,o)).toLowerCase(),a=-1===o?"":i.slice(o+1);if(!n.has(s)||!r&&0===a.length)return{code:50203,message:"invalid path; reason=dir_not_allowed"};if(i.includes("\0")||i.includes("\\")||a.split("/").includes(".."))return{code:50203,message:"invalid path; reason=traversal"};if((new TextEncoder).encode(a).byteLength>$e)return{code:50203,message:"invalid path; reason=name_too_long"};const c=a?a.split("/"):[];return c.length>4?{code:50203,message:"invalid path; reason=depth_exceeded"}:c.some(e=>"."===e||!qe.test(e))?{code:50203,message:"invalid path; reason=name_invalid"}:c.some(e=>e.startsWith("__xhs"))?{code:50203,message:"invalid path; reason=reserved_name"}:{dir:s,relativePath:a,path:a?`${s}/${a}`:s}}function tt(e,t){const n=b(e);if(null===n)return"__DECODE_ERROR__";const r=n.split(/[?#]/,1)[0];if(r.includes("\0")||r.includes("\\")||r.split("/").includes(".."))return"__DECODE_ERROR__";let i;if(e.startsWith("xhsfile://"))i=e.slice(10).split(/[?#]/,1)[0];else{let n;try{n=new URL(e)}catch{return null}if("http:"!==n.protocol&&"https:"!==n.protocol)return null;const r="/_xhsfile_/",o=n.pathname.indexOf(r);if(-1===o)return null;const s=n.pathname.slice(o+r.length),a=s.indexOf("/");if(a<=0||b(s.slice(0,a))!==t)return null;i=s.slice(a+1)}const o=b(i);return null===o?"__DECODE_ERROR__":o}function nt(e,t){if("utf8"===t)return e.length>D?{code:50201,message:"storage quota exceeded; reason=single_over_limit"}:{bytes:(new TextEncoder).encode(e).buffer};if(e.length>Math.ceil(4*D/3)+4)return{code:50201,message:"storage quota exceeded; reason=single_over_limit"};if(!rt(e))return{code:10001,message:"invalid parameter; reason=decode_failed"};try{const t=atob(e),n=new Uint8Array(t.length);for(let e=0;e<t.length;e++)n[e]=t.charCodeAt(e);return{bytes:n.buffer}}catch{return{code:10001,message:"invalid parameter; reason=decode_failed"}}}function rt(e){return e.length%4==0&&/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(e)}function ae(e,t,n,r){return(I.get(e)??0)+n>Ne?{code:50201,message:"storage quota exceeded; reason=session_limit"}:B(t.filter(e=>!e.isDir&&e.path.startsWith("usr/")&&!E(e.path)))-r+n>se?{code:50201,message:"storage quota exceeded; reason=total_over_limit"}:void 0}function ce(e,t){I.set(e,(I.get(e)??0)+t)}function q(e){return new Map(e.map(e=>[e.path,e]))}function ue(e,t){const n=[];for(const r of fe(e)){const e=t.get(r);if(e&&!e.isDir)return{code:50205,message:"not a file"};e||n.push(r)}return{missing:n}}async function le(e,t){for(const n of t)await A(e,n)}function fe(e){const t=e.split("/"),n=[];for(let e=2;e<t.length;e++)n.push(t.slice(0,e).join("/"));return n}function it(e){const t=e.slice(e.lastIndexOf("/")+1),n=t.lastIndexOf("."),r=n>0?t.slice(n):"",i=`${de()}${r}`;return{dir:"usr",relativePath:i,path:`usr/${i}`}}function st(e,t){const n=new Uint8Array(e.byteLength+t.byteLength);return n.set(new Uint8Array(e),0),n.set(new Uint8Array(t),e.byteLength),n.buffer}function ot(e){const t=new Uint8Array(e);let n="";for(let e=0;e<t.length;e+=32768)n+=String.fromCharCode(...t.subarray(e,e+32768));return btoa(n)}function at(e,t){return J(e)??("utf8"===t?"text/plain; charset=utf-8":"application/octet-stream")}function B(e){return e.reduce((e,t)=>e+t.size,0)}function E(e){return e.split("/").some(e=>e.startsWith("__xhs"))}function de(){if(typeof crypto<"u"&&"function"==typeof crypto.randomUUID)return crypto.randomUUID().replace(/-/g,"");let e="";for(let t=0;t<32;t++)e+=Math.floor(16*Math.random()).toString(16);return e}function he(e,t){return`${h.registration.scope}_xhsfile_/${encodeURIComponent(e)}/${t}`}function ct(e){let t;try{t=new URL(e).pathname}catch{return null}const n="/_xhsfile_/",r=t.indexOf(n);if(-1===r)return null;const i=t.slice(r+11),o=i.indexOf("/");if(o<=0)return null;const s=b(i.slice(0,o)),a=b(i.slice(o+1));return!s||!a||a.includes("\0")||a.includes("\\")||a.split("/").includes("..")?null:{scope:s,path:a}}async function ut(e,t){try{const n=await S(e,t);if(n&&!n.isDir&&!E(t)){const e=J(n.bytes);if(e)return re(n.bytes,e)}}catch{}return new Response("Not Found",{status:404,headers:{"Content-Type":"text/plain; charset=utf-8"}})}async function lt(e,t){if(t)try{const e=await h.clients.matchAll({type:"window",includeUncontrolled:!0});Ee(new Set(e.map(e=>e.id)))}catch{}return ve({...e,ownerClientId:t})}function ft(e){if(!e||"object"!=typeof e||!("id"in e))return"";const t=e.id;return"string"==typeof t?t:""}h.addEventListener("install",()=>{h.skipWaiting()}),h.addEventListener("activate",e=>{e.waitUntil(h.clients.claim())}),h.addEventListener("message",e=>{const t=e.data;if(!t||"string"!=typeof t.type)return;const n=t=>e.ports?.[0]?.postMessage(t);if("MOUNT"===t.type){const r=lt(t,ft(e.source)).then(e=>n({type:"MOUNT_ACK",id:t.id,ok:!0,count:e,swVersion:5,fsOps:De}),e=>{const r=e;n({type:"MOUNT_ACK",id:t.id,ok:!1,error:r instanceof Error?r.message:String(e),code:r?.code})});return void e.waitUntil?.(r)}if("FS"===t.type){const r=t,i=Ge(r).then(n,e=>n({type:"FS_ACK",op:r.op,id:r.id,reqId:r.reqId,ok:!1,error:e instanceof Error?e.message:String(e),code:"ERR_UNKNOWN"}));return void e.waitUntil?.(i)}"UNMOUNT"===t.type&&(ze(t.id),xe(t.id))}),h.addEventListener("fetch",e=>{if("worker"===e.request.destination||"sharedworker"===e.request.destination)return void e.respondWith(ke(e.request,e.clientId));if("GET"!==e.request.method)return;const t=ct(e.request.url);if(t)return void e.respondWith(ut(t.scope,t.path));const n=P(e.request.url);if(n)return void e.respondWith(Oe(n.app,n.filePath,e.request));const r=e.clientId||e.resultingClientId;r&&e.respondWith(Be(r,e.request))});
@@ -0,0 +1,143 @@
1
+ ---
2
+ reddoc_shortcut_id: c196078b3a52ef340f243caefaf86021
3
+ reddoc_url: https://docs.xiaohongshu.com/doc/c196078b3a52ef340f243caefaf86021
4
+ ---
5
+
6
+ # Skillbuilder Miniapp 消费指南
7
+
8
+ 技术栈无关的小应用 **PC 预览模拟器** 与 **端上 WebView 能力封印** 运行时。PC 只装 `@skillbuilder/miniapp-simulator`;端上不装 npm,跟版内置 `.iife.min.js`。
9
+
10
+ ---
11
+
12
+ ## 快速开始 · PC(npm)
13
+
14
+ ### 1. 安装
15
+
16
+ ```bash
17
+ pnpm add @skillbuilder/miniapp-simulator@<版本>
18
+ # 快照版:pnpm add @skillbuilder/miniapp-simulator@x.y.z-beta.xxx --tag beta
19
+ ```
20
+
21
+ ### 2. 挂载点
22
+
23
+ ```html
24
+ <div id="preview" style="width:100%;height:700px;"></div>
25
+ ```
26
+
27
+ ### 3. 最小示例
28
+
29
+ ```typescript
30
+ import { createMiniAppSimulator } from "@skillbuilder/miniapp-simulator";
31
+
32
+ const simulator = createMiniAppSimulator({
33
+ mount: "#preview",
34
+ });
35
+
36
+ // create 只配置;入口由 loadApp / loadZip / loadFiles 加载
37
+ await simulator.loadApp("https://example.com/demo-app.html");
38
+ ```
39
+
40
+ 加载完成后,模拟器在 `#preview` 内渲染手机舞台并打开小工具。切换 URL 无需重建实例:
41
+
42
+ ```typescript
43
+ await simulator.loadApp("https://example.com/other-app.html");
44
+ ```
45
+
46
+ 销毁:
47
+
48
+ ```typescript
49
+ simulator.destroy();
50
+ ```
51
+
52
+ ---
53
+
54
+ ## 快速开始 · 端上 App(iOS / Android)
55
+
56
+ **不安装 npm。** 取封印 IIFE(`device-capabilities-seal.iife.min.js`)全文,跟版内置。
57
+
58
+ | 步骤 | 说明 |
59
+ | --- | --- |
60
+ | 1 | 取得 `device-capabilities-seal.iife.min.js` 全文(封印产物发布 / 内部 tarball 解压) |
61
+ | 2 | `document-start` 注入 IIFE(`WKUserScript` / `addDocumentStartJavaScript`) |
62
+ | 3 | 可选:IIFE **之前**注入白名单配置 |
63
+
64
+ ```javascript
65
+ // 可选:须在 IIFE 之前
66
+ window.__MINI_APP_SEAL_CONFIG__ = {"allow":["camera","microphone","photo-library"]};
67
+ // 随后注入 device-capabilities-seal.iife.min.js 全文
68
+ ```
69
+
70
+ 公共源直链示例(封印产物发布地址):
71
+
72
+ ```
73
+ https://fe-static.xhscdn.com/minitool/js-sdk/<时间戳>/<版本>/device-capabilities-seal.iife.min.js
74
+ ```
75
+
76
+ ---
77
+
78
+ ## 谁用哪个包
79
+
80
+ | 终端 | 消费方式 | 产物 |
81
+ | --- | --- | --- |
82
+ | PC 前端 | `pnpm add @skillbuilder/miniapp-simulator` | `lib/`(ESM) |
83
+ | iOS / Android | 跟版内置脚本,不装 npm | `device-capabilities-seal.iife.min.js` |
84
+
85
+ PC 侧无需单独提供封印脚本(已打进模拟器 bundle)。
86
+
87
+ ---
88
+
89
+ ## 常用能力(PC)
90
+
91
+ ```typescript
92
+ import { createMiniAppSimulator, MiniAppCapability } from "@skillbuilder/miniapp-simulator";
93
+
94
+ const simulator = createMiniAppSimulator({
95
+ mount: "#preview",
96
+ viewport: { width: 390, height: 844 },
97
+ deviceCapabilities: {
98
+ sealAllow: [
99
+ MiniAppCapability.Camera,
100
+ MiniAppCapability.Microphone,
101
+ MiniAppCapability.PhotoLibrary,
102
+ ],
103
+ },
104
+ onRenderDone: (e) => console.log("首屏", e.duration, "ms"),
105
+ });
106
+
107
+ await simulator.loadApp("https://example.com/demo-app.html"); // 或 loadZip / loadFiles
108
+ await simulator.switchContentMode("half"); // 切换内容模式(half / fullscreen)
109
+ ```
110
+
111
+ 更多 API 见仓库 `packages/miniapp-simulator/docs/sdk-usage.md`。
112
+
113
+ ---
114
+
115
+ ## 调试面板(`debug: true`)
116
+
117
+ 开发联调时传 `debug: true`,可用 `Shift + Command + U`(macOS)/ `Shift + Ctrl + U`(其他平台)在模拟器内唤起内置调试面板:
118
+
119
+ ```typescript
120
+ const simulator = createMiniAppSimulator({
121
+ mount: "#preview",
122
+ debug: true,
123
+ });
124
+ await simulator.loadApp("https://example.com/demo-app.html");
125
+ ```
126
+
127
+ 面板覆盖两块能力:
128
+
129
+ 1. **能力管控**:枚举全部被封印 / 放行的设备能力(`MiniAppCapability`),可在面板内动态开关并自动重新加载小应用(等价于运行时调用 `setDeviceCapabilities({ sealAllow })`)。
130
+ 2. **运行日志**:实时展示生命周期、`console`、跳转拦截等关键事件。
131
+
132
+ `debug` 默认 `false`,不开启时无任何额外开销,可安全用于生产消费方。
133
+
134
+ ---
135
+
136
+ ## 版本与发布
137
+
138
+ | 类型 | npm tag | 版本示例 |
139
+ | --- | --- | --- |
140
+ | 正式版 | `latest` | `1.2.3` |
141
+ | 快照版 | `beta` | `1.2.3-beta.<timestamp>` |
142
+
143
+ 发布通知见 RedCity;安装命令与脚本下载链接以当次通知为准。
@@ -0,0 +1,21 @@
1
+ # Electron 审核模拟器接入说明
2
+
3
+ 审核模拟器位于 `apps/mini-app-review-simulator`,核心渲染能力来自:
4
+
5
+ ```ts
6
+ import { createElectronReviewSimulator } from "@skillbuilder/miniapp-simulator/adapters/electron";
7
+ ```
8
+
9
+ 审核壳子负责:
10
+
11
+ - 收集审核员输入的 `appUrl`、`appId`、`versionId`。
12
+ - 管理 Electron 窗口、preload 和 IPC。
13
+ - 存储运行事件、截图和导出记录。
14
+
15
+ SDK 负责:
16
+
17
+ - 创建 `webview` 容器。
18
+ - 加载小应用 URL。
19
+ - 派发 `load`、`error`、`navigation`、`console`、`capture` 事件。
20
+
21
+ 最终审核结论不在 SDK 内完成,应由审核规则和审核服务承接。
@@ -0,0 +1,195 @@
1
+ # 外部 CLI 调试工作台接入
2
+
3
+ 外部 CLI 通过 Node 服务入口集成完整工作台,**只需直接依赖 `@skillbuilder/miniapp-simulator` 一个包**。
4
+ 工作台页面、浏览器 SDK、资源与类型声明随包提供;公开运行依赖由 npm 自动安装,不需要安装其他仓库内的包,也不需要 Electron。
5
+
6
+ ## 安装与入口
7
+
8
+ ```shell
9
+ # 发布后安装明确版本;未发布前可使用维护方提供的 .tgz 文件。
10
+ npm install @skillbuilder/miniapp-simulator@<发布版本>
11
+ ```
12
+
13
+ | 入口 | 使用位置 | 职责 |
14
+ | --- | --- | --- |
15
+ | `@skillbuilder/miniapp-simulator/workbench/server` | CLI 的 Node 进程 | `startWorkbenchServer()` 启动本地服务,返回 `url`、`close()` |
16
+ | `@skillbuilder/miniapp-simulator/workbench` | 浏览器 | `createMiniAppWorkbench()` 创建工作台,内部使用 Remote Simulator |
17
+ | `@skillbuilder/miniapp-simulator/workbench.html` | SDK 内部托管 | 完整工作台页面;CLI 不必自己读取或复制 |
18
+
19
+ 消费环境为 Node.js 20+。Node 入口使用 ESM:直接 `import`;CommonJS CLI 使用 `await import(...)`,不约定同步 `require()`。
20
+ 普通 CLI 只调用 Node 服务入口,后两项由 SDK 完成。不要把浏览器入口导入 CLI 服务,也不要只拷贝 `workbench.js` 而遗漏它引用的包内 chunks。
21
+
22
+ ## 命令参数
23
+
24
+ ```shell
25
+ xxx dev --dir /absolute/path/to/minitool
26
+ ```
27
+
28
+ | CLI 参数 | 默认值 | 对应行为 |
29
+ | --- | --- | --- |
30
+ | `--dir <path>` | 当前目录 | 传给 `startWorkbenchServer({ rootDir })`,包含 HTML 及其静态资源 |
31
+ | `--no-open` | 不传时自动打开浏览器 | CLI 自己控制是否打开 SDK 返回的 `server.url`,不是 SDK 参数 |
32
+ | `-h` / `--help` | — | CLI 输出帮助后退出,不启动服务 |
33
+
34
+ 优先使用根目录的 `index.html`;不存在时,自动使用根目录中唯一的 HTML 文件。没有 HTML 或存在多个候选时,报错提示。
35
+ 目录中的 HTML、JS、CSS、图片等资源直接以文件表加载,不生成 ZIP。需要编译的项目应传入构建产物目录,例如 `./dist`。
36
+ 隐藏文件和符号链接不纳入清单;请仅指定用于调试的产物目录。
37
+
38
+ 上述是本期 Mock 支持、建议正式 CLI 保持一致的全部用户参数。
39
+ `--port` / `--host` 不开放:服务固定监听 `127.0.0.1`,端口自动分配;`--entry` 不开放:自动识别入口。
40
+ 设备和场景在工作台切换,不增加 `--width` / `--height` / `--scene`;文件监听默认开启,不增加 `--watch`。
41
+ Runtime 服务地址、业务线和小工具 ID 由接入方注入 SDK,不作为面向创作者的命令参数。
42
+
43
+ ## 本地服务职责
44
+
45
+ - CLI 调用 `startWorkbenchServer({ rootDir })`,负责命令参数、打开浏览器和退出时调用 `close()`。
46
+ - SDK 的 Node 入口负责识别 HTML 入口,以及托管工作台、配置、SDK、文件清单和原始资源;服务运行在 CLI 的 Node 进程中。
47
+ - 浏览器加载工作台后调用 `createMiniAppWorkbench()`,获取目录文件并通过 `loadFiles()` 交给 Remote Simulator / Service Worker。
48
+
49
+ 本地服务只监听 `127.0.0.1`,端口自动分配。SDK 返回服务地址和关闭函数,不注册进程信号、不输出日志。
50
+ HTTP 服务使用 Koa,路由与文件响应分别交给 `@koa/router`、`@koa/send`;目录入口识别与文件清单独立维护,Node 依赖不进入浏览器构建。
51
+
52
+ ## Node API
53
+
54
+ ```ts
55
+ import { startWorkbenchServer } from "@skillbuilder/miniapp-simulator/workbench/server";
56
+
57
+ const server = await startWorkbenchServer({ rootDir: "/absolute/path/to/minitool/dist" });
58
+ console.log(server.url);
59
+
60
+ // CLI 退出时调用;可重复调用。
61
+ await server.close();
62
+ ```
63
+
64
+ | SDK 参数 | 必填 | 默认值 | 来源 |
65
+ | --- | --- | --- | --- |
66
+ | `rootDir: string` | 是 | — | CLI 的 `--dir`,省略时由 CLI 传当前目录 |
67
+ | `remote.serviceUrl: string` | 否 | 正式 Simulator 服务 | 接入方统一配置的 HTTP(S) Simulator 服务地址 |
68
+ | `remote.appId: string` | 否 | `local-workbench` | 接入方的小工具上下文;本地调试可省略 |
69
+ | `remote.businessLine: BusinessLine` | 否 | `BusinessLine.skillDesignable` | 接入方固定配置,不从 Agent 任意输入透传 |
70
+
71
+ 需要自定义远程上下文时,`BusinessLine` 也从同一个 Node 入口导入:
72
+
73
+ ```ts
74
+ import { BusinessLine, startWorkbenchServer } from "@skillbuilder/miniapp-simulator/workbench/server";
75
+
76
+ const server = await startWorkbenchServer({
77
+ rootDir: "/absolute/path/to/minitool/dist",
78
+ remote: { serviceUrl: simulatorServiceUrl, appId: "my-minitool", businessLine: BusinessLine.skillDesignable },
79
+ });
80
+ ```
81
+
82
+ `server.url` 是实际工作台地址。CLI 负责输出地址、按 `--no-open` 决定是否打开浏览器,并在 `SIGINT` / `SIGTERM` 时调用 `server.close()`。
83
+ SDK 不接管 CLI 进程、账号登录或远程平台鉴权;传入目录应是允许调试的产物目录,不应包含凭据或其他无关文件。
84
+
85
+ 该入口面向 Node.js 20+,构建为独立 `.mjs` 模块并提供类型声明;浏览器侧继续从 `workbench` 入口导入。
86
+ 文件清单与资源按请求读取。Node 入口使用 Chokidar 递归监听调试目录,HTML、JS(含 MJS / CJS)、CSS 的新增、修改、删除会自动触发小工具重载。
87
+ 等待文件写入稳定后再防抖 200ms,合并连续写入;隐藏目录、`node_modules` 和符号链接不参与监听。
88
+ 通知通过同源 SSE 发送,工作台重新调用 `loadFiles()`,不刷新工作台页面,也不重新创建 Remote Simulator。
89
+ 加载期间的新变更会合并并串行执行;SSE 断线自动重连,重连后重新读取目录。“重新运行”仍可手动使用。
90
+ 这是产物整体重载,不是模块级 HMR;小工具的内存状态会重置。关闭服务会同时释放文件监听和 SSE 连接。
91
+
92
+ ## HTTP 托管契约
93
+
94
+ 以下路由由 Node 服务入口统一维护,正式 CLI 与 Mock 无需各自实现:
95
+
96
+ | 路径 | 内容 |
97
+ | --- | --- |
98
+ | `/` | `workbench.html` |
99
+ | `/__miniapp_workbench__/config.json` | 本次会话配置 |
100
+ | `/__miniapp_workbench__/sdk/*` | npm 包内的 `lib/*` |
101
+ | `/__miniapp_workbench__/manifest.json` | 最新的入口名称与目录文件清单 |
102
+ | `/__miniapp_workbench__/files/*` | 调试目录中的原始文件,支持文本与二进制资源 |
103
+ | `/__miniapp_workbench__/events` | SSE:连接时及文件变更后发送 `reload`,监听异常发送 `watch-error` |
104
+
105
+ 配置响应使用 `application/json`,结构固定为:
106
+
107
+ ```json
108
+ {
109
+ "schemaVersion": 2,
110
+ "projectName": "demo-minitool",
111
+ "viewport": { "width": 393, "height": 852 },
112
+ "sdkUrl": "/__miniapp_workbench__/sdk/workbench.js",
113
+ "remote": {
114
+ "serviceUrl": "https://miniapp-sandbox.xiaohongshu.com/simulator/",
115
+ "appId": "local-workbench",
116
+ "businessLine": "skill-designable",
117
+ "tracker": false
118
+ },
119
+ "artifact": {
120
+ "type": "files",
121
+ "url": "/__miniapp_workbench__/manifest.json",
122
+ "baseUrl": "/__miniapp_workbench__/files/",
123
+ "eventsUrl": "/__miniapp_workbench__/events"
124
+ }
125
+ }
126
+ ```
127
+
128
+ 文件清单响应示例:
129
+
130
+ ```json
131
+ {
132
+ "entry": "index.html",
133
+ "files": ["index.html", "main.js", "styles.css", "images/logo.png"]
134
+ }
135
+ ```
136
+
137
+ 页面读取清单后,按路径逐段 URL 编码,从 `artifact.baseUrl` 获取文件的 `ArrayBuffer`,
138
+ 组成“相对路径 → 文件内容”的映射,调用 `workbench.loadFiles(files, { entry })`。自动或手动重载时均重复此流程,不复用旧文件表。
139
+ 页面订阅 `artifact.eventsUrl`,首个 `reload` 通知负责初次加载;后续通知通过 `workbench.reload()` 复用同一条串行重载链路。
140
+
141
+ 页面先读取配置,再通过 `import(config.sdkUrl)` 动态加载工作台 SDK。薄 SDK 连接 `serviceUrl`,请求
142
+ `runtime-manifest` 取得兼容 Runtime;Runtime 注册 Simulator Server 同源的版本化 Service Worker,
143
+ 并通过 `loadFiles` 将本地文件挂载到虚拟文件系统。CLI 不托管 Service Worker。
144
+
145
+ 本地 Agent 入口不使用 `loadZip()`;SDK 的 ZIP 加载 API 保留,供其他既有场景使用。
146
+
147
+ ## 模拟场景
148
+
149
+ 工作台统一使用 SDK 的 `shell: "rednote"`,不提供 `none` 裸预览;手机边框、状态栏及返回等导航控件均由 SDK 渲染。
150
+
151
+ | 场景 | 复用的 SDK 能力 |
152
+ | --- | --- |
153
+ | 全屏小工具(默认) | `contentMode: "fullscreen"`,保留返回、分享、更多等控件 |
154
+ | 半屏小工具 | `switchContentMode("half")`,在笔记页上展示小工具半屏 |
155
+ | 笔记页 | `redirectTo("note")`,可从原生入口再次唤起小工具 |
156
+ | 个人页 | `redirectTo("profile")`,保留个人页返回与小工具卡片入口 |
157
+
158
+ 场景与设备尺寸独立切换;文件重载保持当前选定场景。调用方也可使用
159
+ `workbench.setScene("fullscreen" | "half" | "note" | "profile")` 切换。工作台只编排 SDK,不另做导航控件。
160
+
161
+ ## 仓库内 Mock
162
+
163
+ 先构建 npm 产物,再运行 Mock:
164
+
165
+ ```shell
166
+ pnpm --filter @skillbuilder/miniapp-simulator build:npm
167
+ pnpm --filter @skillbuilder/miniapp-simulator mock:cli-dev -- \
168
+ --dir /absolute/path/to/minitool/dist
169
+ ```
170
+
171
+ 也可以直接模拟完整子命令:
172
+
173
+ ```shell
174
+ node packages/miniapp-simulator/scripts/mock-cli-dev.mjs dev \
175
+ --dir /absolute/path/to/minitool/dist \
176
+ --no-open
177
+ ```
178
+
179
+ Mock 只解析根目录、调用 `startWorkbenchServer()`、唤起浏览器并在退出时关闭服务;
180
+ 工作台通过 `createRemoteMiniAppSimulator` 使用远端 Runtime 与 Service Worker。
181
+
182
+ ## 打包与外部消费验证
183
+
184
+ 维护方执行:
185
+
186
+ ```shell
187
+ pnpm --dir packages/miniapp-simulator pack --pack-destination /absolute/path/to/output
188
+ ```
189
+
190
+ `prepack` 自动构建工作台、Node 入口、类型声明和兼容既有入口的 SW 产物,不上传 CDN 或执行 npm 发布。
191
+ 建议在仓库外新建空项目,只安装本次 tarball,运行依赖从 npm 官方源下载,避免 workspace link 或已有根目录依赖掩盖漏包问题。
192
+ 手动验收导出文件、ESM / CommonJS 动态导入、Node / 浏览器类型、HTTP 资源、SSE 文件变更通知与退出清理。
193
+
194
+ **发布前仍需确认:**目标 registry、scope 发布权限、版本号及外发审批;远程 Simulator 默认是正式地址,仍须从实际外部网络验证可达性和访问策略。
195
+ 单包集成不等于离线运行:工作台仍需联网加载远程 Runtime / Service Worker。安装包测试通过,不代表公共源已经发布或外网环境已经验收。