@skillbuilder/miniapp-simulator 0.0.0-beta-20261023084421 → 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 (37) hide show
  1. package/README.md +52 -2
  2. package/dist/sw/miniapp-simulator-service-worker.js +487 -371
  3. package/dist/sw/miniapp-simulator-service-worker.min.js +1 -1
  4. package/docs/external-cli-workbench.md +5 -7
  5. package/docs/npm-integration-verification.md +1 -11
  6. package/docs/remote-architecture.md +3 -2
  7. package/docs/worker-runtime.md +62 -0
  8. package/lib/{client-BZtD2VtP.mjs → client-CO-UjIY3.mjs} +141 -138
  9. package/lib/index-B5oixYXr.mjs +574 -0
  10. package/lib/index.js +1152 -1075
  11. package/lib/remote-client.js +2 -2
  12. package/lib/types/capabilities/constants.d.ts +1 -0
  13. package/lib/types/capabilities/csp-snippet.d.ts +2 -0
  14. package/lib/types/capabilities/security-head-snippet.d.ts +3 -2
  15. package/lib/types/index.d.ts +8 -1
  16. package/lib/types/injection/types.d.ts +2 -0
  17. package/lib/types/remote/protocol.d.ts +7 -2
  18. package/lib/types/renderer/content-host/iframe-content-host.d.ts +1 -0
  19. package/lib/types/sw/protocol.d.ts +3 -1
  20. package/lib/types/sw/served-entry.d.ts +1 -1
  21. package/lib/types/sw/service-worker-host.d.ts +1 -0
  22. package/lib/types/sw/worker/globals.d.ts +1 -0
  23. package/lib/types/sw/worker/mount-store.d.ts +5 -0
  24. package/lib/types/types/options.d.ts +8 -1
  25. package/lib/types/workbench/server/index.d.ts +1 -1
  26. package/lib/types/worker/adapters/csp-policy.d.ts +7 -0
  27. package/lib/types/worker/adapters/host-lifecycle.d.ts +5 -0
  28. package/lib/types/worker/adapters/mount-recovery.d.ts +2 -0
  29. package/lib/types/worker/adapters/package-policy.d.ts +12 -0
  30. package/lib/types/worker/adapters/page-lifecycle.d.ts +1 -0
  31. package/lib/types/worker/adapters/protocol.d.ts +49 -0
  32. package/lib/types/worker/adapters/scripts.d.ts +2 -0
  33. package/lib/types/worker/adapters/service-worker.d.ts +2 -0
  34. package/lib/workbench-server.mjs +1 -1
  35. package/lib/workbench.js +1 -1
  36. package/package.json +1 -1
  37. package/lib/index-BW85h7kT.mjs +0 -505
@@ -1 +1 @@
1
- const ue=["fsWriteTempFile","fsSaveFile","fsWriteFile","fsAppendFile","fsReadFile","fsReadDir","fsStatFile","fsUnlink","fsMkdir","fsGetStorageInfo"],le={"image/png":"png","image/jpeg":"jpg","image/webp":"webp","image/gif":"gif","video/mp4":"mp4","video/quicktime":"mov","video/webm":"webm"};function V(e){const t=new Uint8Array(e);return A(t,[137,80,78,71,13,10,26,10])?"image/png":A(t,[255,216,255])?"image/jpeg":E(t,0,"GIF87a")||E(t,0,"GIF89a")?"image/gif":E(t,0,"RIFF")&&E(t,8,"WEBP")?"image/webp":E(t,4,"ftyp")?"qt "===Y(t,8,12)?"video/quicktime":"video/mp4":A(t,[26,69,223,163])?"video/webm":void 0}function fe(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=le[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 A(e,t){return t.every((t,n)=>e[n]===t)}function E(e,t,n){return Y(e,t,t+n.length)===n}function Y(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 de="miniapp-simulator-fs",he=1,d="files";let b=null;function _(){return b||(b=new Promise((e,t)=>{const n=indexedDB.open(de,1);n.onupgradeneeded=()=>{const e=n.result;if(!e.objectStoreNames.contains(d)){const t=e.createObjectStore(d,{keyPath:["scope","path"]});t.createIndex("by_scope","scope",{unique:!1}),t.createIndex("by_createdAt","createdAt",{unique:!1})}},n.onsuccess=()=>e(n.result),n.onerror=()=>{b=null,t(n.error??new Error("indexedDB open failed"))},n.onblocked=()=>{b=null,t(new Error("indexedDB open blocked"))}}),b)}function X(e){return new Promise((t,n)=>{e.onsuccess=()=>t(e.result),e.onerror=()=>n(e.error??new Error("indexedDB request failed"))})}function I(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 B(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 Z(e,t,n,r="application/octet-stream"){const i=(await _()).transaction(d,"readwrite");i.objectStore(d).put(B(e,t,n,r,!1)),await I(i)}async function L(e,t){const n=(await _()).transaction(d,"readwrite");n.objectStore(d).put(B(e,t,new ArrayBuffer(0),"application/x-directory",!0)),await I(n)}async function S(e,t){const n=(await _()).transaction(d,"readonly").objectStore(d),r=await X(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 _();return(await X(t.transaction(d,"readonly").objectStore(d).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 pe(e,t){const n=(await _()).transaction(d,"readwrite");n.objectStore(d).delete([e,t]),await I(n)}async function me(e,t,n,r){const i=(await _()).transaction(d,"readwrite"),o=i.objectStore(d);o.put(B(e,n,r.bytes,r.mime,!1)),o.delete([e,t]),await I(i)}const g=self,ge="index.html",ye={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"},we={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 be(e){const t=e.split(".").pop()?.toLowerCase()??"";return ye[t]??we[t]??"application/octet-stream"}function w(e){try{return decodeURIComponent(e)}catch{return null}}function k(e){const t=[];for(const n of e.replace(/\\/g,"/").split("/"))if(""!==n&&"."!==n){if(".."===n)return"";t.push(n)}return t.join("/")}const m=new Map,q=16;function _e(e){if(!m.has(e.id)&&m.size>=q){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=k(n);e&&t.set(e,{bytes:r,mime:be(e)})}return m.set(e.id,{files:t,injectHead:e.injectHead,csp:e.csp,remoteBase:e.remoteBase??"",fsScope:e.fsScope??"",ownerClientId:e.ownerClientId??""}),t.size}function ve(e){m.delete(e)}function xe(e){let t=0;for(const[n,r]of m)!r.ownerClientId||e.has(r.ownerClientId)||(m.delete(n),t+=1);return t}function Ee(e){let t;try{t=new URL(e).pathname}catch{return null}for(const[e,n]of m){const r=`/${e}/`,i=t.indexOf(r);if(-1===i)continue;let o=w(t.slice(i+r.length));if(null===o)return null;(""===o||o.endsWith("/"))&&(o+=ge);const s=k(o);return s?{app:n,filePath:s}:null}return null}function Re(e){let t;try{t=new URL(e).pathname}catch{return null}for(const[e,n]of m)if(t.includes(`/${e}/`))return n;return null}function Se(e,t,n){if(e.files.has(t))return O(e,t);if(e.remoteBase){const r=new URL(t,e.remoteBase);return r.search=new URL(n.url).search,J(r.href)}return O(e,t)}async function De(e,t){try{const n=await g.clients.get(e);if(n){const e=Re(n.url);if(e){const n=new URL(t.url),r=w(n.pathname),i=null===r?"":k(r);if(i&&e.files.has(i))return O(e,i);if(e.remoteBase){return J(new URL(n.pathname+n.search,new URL(e.remoteBase).origin).href)}}}}catch{}return fetch(t)}function Q(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 O(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=Pe(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 Q(n.bytes,n.mime)}async function J(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 Pe(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 ee=52428800,D=2097152,U=2097152,Ie=209715200,Fe=4,Te=255,z=1e3,N=new Set(["tmp","usr"]),F=new Set(["usr"]),Me=/^[A-Za-z0-9._-]{1,64}$/,P=new Map;let G=Promise.resolve();function T(e){const t=G.then(e,e);return G=t.then(()=>{},()=>{}),t}function Ae(e){P.delete(e)}async function Le(e){const t=t=>({type:"FS_ACK",op:e.op,id:e.id,reqId:e.reqId,...t});if(!m.has(e.id))return t({ok:!1,error:"simulator not ready",code:"ERR_NOT_MOUNTED"});try{return t(await Oe(e))}catch{return t({ok:!1,code:50101,error:"storage io error"})}}async function Oe(e){const t=e.fsScope||e.id;switch(e.op){case"fsWriteTempFile":return Ue(t,e);case"fsSaveFile":return Ce(t,e);case"fsWriteFile":return H(t,e,!1);case"fsAppendFile":return H(t,e,!0);case"fsReadFile":return Be(t,e);case"fsReadDir":return ke(t,e);case"fsStatFile":return Ne(t,e);case"fsUnlink":return We(t,e);case"fsMkdir":return je(t,e);case"fsGetStorageInfo":return $e(t);default:return{ok:!1,error:`unsupported operation: ${e.op}`,code:"ERR_UNSUPPORTED_OP"}}}async function Ue(e,t){const n=fe(t.data);if("error"in n)return{ok:!1,error:n.error,code:n.code};const r=`tmp/${ae()}.${n.ext}`;return await Z(e,r,n.bytes,n.mime),{ok:!0,result:{path:r,url:ce(e,r)}}}async function Ce(e,t){const n=h(t.tempFilePath,"tempFilePath");if(n)return n;if(void 0!==t.filePath&&null!==t.filePath){const e=h(t.filePath,"filePath");if(e)return e}const r=y(t.tempFilePath,e,new Set(["tmp"]),!1);if("code"in r)return u(r);const i=void 0===t.filePath||null===t.filePath?He(r.path):y(t.filePath,e,F,!1);return"code"in i?u(i):T(async()=>{const n=await S(e,r.path);if(!n)return u({code:50202,message:"file not found; reason=not_found"});if(n.isDir)return u({code:50205,message:"not a file"});const o=await v(e),s=j(o);if(s.has(i.path))return u({code:50204,message:"file already exists"});const a=ie(i.path,s);if("code"in a)return u(a);const c=ne(t.id,o,n.bytes.byteLength,0);return c?u(c):(await se(e,a.missing),await me(e,r.path,i.path,n),re(t.id,n.bytes.byteLength),{ok:!0,result:{savedFilePath:ce(e,i.path)}})})}async function H(e,t,n){const r=h(t.filePath,"filePath");if(r)return r;const i=te(t.data,"data",!0);if(i)return i;const o=h(t.encoding,"encoding");if(o)return o;const s=y(t.filePath,e,F,!1);if("code"in s)return u(s);if("base64"!==t.encoding&&"utf8"!==t.encoding)return u({code:10001,message:"invalid parameter; reason=encoding_unsupported"});const a=ze(t.data,t.encoding);return"code"in a?u(a):a.bytes.byteLength>D?u({code:50201,message:"storage quota exceeded; reason=single_over_limit"}):T(async()=>{const r=await v(e),i=j(r),o=await S(e,s.path);if(o?.isDir)return u({code:50205,message:"not a file"});const c=ie(s.path,i);if("code"in c)return u(c);const f=n?0:o?.bytes.byteLength??0,d=ne(t.id,r,a.bytes.byteLength,f);if(d)return u(d);await se(e,c.missing);let l=a.bytes;return n&&o&&(l=Ke(o.bytes,a.bytes)),await Z(e,s.path,l,Ye(l,t.encoding)),re(t.id,a.bytes.byteLength),{ok:!0,result:{writtenBytes:a.bytes.byteLength}}})}async function Be(e,t){const n=h(t.filePath,"filePath");if(n)return n;const r=h(t.encoding,"encoding");if(r)return r;const i=K(t.position,"position");if(i)return i;const o=K(t.length,"length");if(o)return o;const s=y(t.filePath,e,N,!1);if("code"in s)return u(s);if("base64"!==t.encoding&&"utf8"!==t.encoding)return u({code:10001,message:"invalid parameter; reason=encoding_unsupported"});const a=void 0===t.position?0:t.position,c=void 0===t.length?U:t.length;if(!Number.isSafeInteger(a)||a<0||!Number.isSafeInteger(c)||c<0||c>U)return u({code:10001,message:"invalid parameter; reason=range_invalid"});const f=await S(e,s.path);if(!f)return u({code:50202,message:"file not found"});if(f.isDir)return u({code:50205,message:"not a file"});const d=Math.min(f.bytes.byteLength,a+c),l=a>=f.bytes.byteLength?new ArrayBuffer(0):f.bytes.slice(a,d);let p;if("base64"===t.encoding)p=Ve(l);else try{p=new TextDecoder("utf-8",{fatal:!0}).decode(l)}catch{return u({code:10001,message:"invalid parameter; reason=decode_failed"})}return{ok:!0,result:{data:p,bytesRead:l.byteLength,eof:a+l.byteLength>=f.bytes.byteLength}}}async function ke(e,t){const n=h(t.dirPath,"dirPath");if(n)return n;const r=y(t.dirPath,e,N,!0);if("code"in r)return u(r);const i=await v(e);if(r.relativePath){const e=i.find(e=>e.path===r.path);if(!e)return u({code:50202,message:"file not found"});if(!e.isDir)return u({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("/")&&!R(e.path)}).sort((e,t)=>e.path.localeCompare(t.path));return{ok:!0,result:{files:s.slice(0,z).map(e=>({name:e.path.slice(o.length),size:e.isDir?0:e.size,lastModified:e.lastModified,isDir:e.isDir})),truncated:s.length>z}}}async function Ne(e,t){const n=h(t.filePath,"filePath");if(n)return n;const r=y(t.filePath,e,N,!1);if("code"in r)return u(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}}:u({code:50202,message:"file not found"})}async function We(e,t){const n=h(t.filePath,"filePath");if(n)return n;const r=y(t.filePath,e,F,!1);return"code"in r?u(r):T(async()=>{const t=await S(e,r.path);return t?t.isDir?u({code:50205,message:"not a file"}):(await pe(e,r.path),{ok:!0,result:{}}):u({code:50202,message:"file not found"})})}async function je(e,t){const n=h(t.dirPath,"dirPath");if(n)return n;if(void 0!==t.recursive&&"boolean"!=typeof t.recursive)return W("recursive");const r=y(t.dirPath,e,F,!1);if("code"in r)return u(r);const i=!0===t.recursive;return T(async()=>{const t=j(await v(e)),n=t.get(r.path);if(n)return n.isDir?i?{ok:!0,result:{}}:u({code:50204,message:"file already exists"}):u({code:50205,message:"not a file"});const o=oe(r.path);for(const e of o){const n=t.get(e);if(n&&!n.isDir)return u({code:50205,message:"not a file"});if(!n&&!i)return u({code:50202,message:"file not found"})}if(i)for(const n of o)t.has(n)||await L(e,n);return await L(e,r.path),{ok:!0,result:{}}})}async function $e(e){const t=await v(e),n=t.filter(e=>!e.isDir&&e.path.startsWith("usr/")&&!R(e.path)),r=t.filter(e=>!e.isDir&&e.path.startsWith("tmp/")&&!R(e.path));return{ok:!0,result:{usedBytes:C(n),limitBytes:ee,fileCount:n.length,tmpUsedBytes:C(r),writeChunkMaxBytes:D,readChunkMaxBytes:U}}}function h(e,t){return te(e,t,!1)}function te(e,t,n){if("string"!=typeof e||!(n||e.length>0))return W(t)}function K(e,t){if(void 0!==e&&"number"!=typeof e)return W(t)}function W(e){return{ok:!1,code:10001,error:`invalid parameter; param=${e}`}}function u(e){return{ok:!1,code:e.code,error:e.message}}function y(e,t,n,r){const i=qe(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>Te)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||!Me.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 qe(e,t){const n=w(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||w(s.slice(0,a))!==t)return null;i=s.slice(a+1)}const o=w(i);return null===o?"__DECODE_ERROR__":o}function ze(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(!Ge(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 Ge(e){return e.length%4==0&&/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(e)}function ne(e,t,n,r){return(P.get(e)??0)+n>Ie?{code:50201,message:"storage quota exceeded; reason=session_limit"}:C(t.filter(e=>!e.isDir&&e.path.startsWith("usr/")&&!R(e.path)))-r+n>ee?{code:50201,message:"storage quota exceeded; reason=total_over_limit"}:void 0}function re(e,t){P.set(e,(P.get(e)??0)+t)}function j(e){return new Map(e.map(e=>[e.path,e]))}function ie(e,t){const n=[];for(const r of oe(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 se(e,t){for(const n of t)await L(e,n)}function oe(e){const t=e.split("/"),n=[];for(let e=2;e<t.length;e++)n.push(t.slice(0,e).join("/"));return n}function He(e){const t=e.slice(e.lastIndexOf("/")+1),n=t.lastIndexOf("."),r=n>0?t.slice(n):"",i=`${ae()}${r}`;return{dir:"usr",relativePath:i,path:`usr/${i}`}}function Ke(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 Ve(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 Ye(e,t){return V(e)??("utf8"===t?"text/plain; charset=utf-8":"application/octet-stream")}function C(e){return e.reduce((e,t)=>e+t.size,0)}function R(e){return e.split("/").some(e=>e.startsWith("__xhs"))}function ae(){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 ce(e,t){return`${g.registration.scope}_xhsfile_/${encodeURIComponent(e)}/${t}`}function Xe(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=w(i.slice(0,o)),a=w(i.slice(o+1));return!s||!a||a.includes("\0")||a.includes("\\")||a.split("/").includes("..")?null:{scope:s,path:a}}async function Ze(e,t){try{const n=await S(e,t);if(n&&!n.isDir&&!R(t)){const e=V(n.bytes);if(e)return Q(n.bytes,e)}}catch{}return new Response("Not Found",{status:404,headers:{"Content-Type":"text/plain; charset=utf-8"}})}async function Qe(e,t){if(t)try{const e=await g.clients.matchAll({type:"window",includeUncontrolled:!0});xe(new Set(e.map(e=>e.id)))}catch{}return _e({...e,ownerClientId:t})}function Je(e){if(!e||"object"!=typeof e||!("id"in e))return"";const t=e.id;return"string"==typeof t?t:""}g.addEventListener("install",()=>{g.skipWaiting()}),g.addEventListener("activate",e=>{e.waitUntil(g.clients.claim())}),g.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=Qe(t,Je(e.source)).then(e=>n({type:"MOUNT_ACK",id:t.id,ok:!0,count:e,swVersion:3,fsOps:ue}),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=Le(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&&(Ae(t.id),ve(t.id))}),g.addEventListener("fetch",e=>{if("GET"!==e.request.method)return;const t=Xe(e.request.url);if(t)return void e.respondWith(Ze(t.scope,t.path));const n=Ee(e.request.url);if(n)return void e.respondWith(Se(n.app,n.filePath,e.request));const r=e.clientId||e.resultingClientId;r&&e.respondWith(De(r,e.request))});
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))});
@@ -64,7 +64,7 @@ await server.close();
64
64
  | SDK 参数 | 必填 | 默认值 | 来源 |
65
65
  | --- | --- | --- | --- |
66
66
  | `rootDir: string` | 是 | — | CLI 的 `--dir`,省略时由 CLI 传当前目录 |
67
- | `remote.serviceUrl: string` | 否 | Beta Simulator 服务 | 接入方统一配置的 HTTP(S) Simulator 服务地址 |
67
+ | `remote.serviceUrl: string` | 否 | 正式 Simulator 服务 | 接入方统一配置的 HTTP(S) Simulator 服务地址 |
68
68
  | `remote.appId: string` | 否 | `local-workbench` | 接入方的小工具上下文;本地调试可省略 |
69
69
  | `remote.businessLine: BusinessLine` | 否 | `BusinessLine.skillDesignable` | 接入方固定配置,不从 Agent 任意输入透传 |
70
70
 
@@ -111,7 +111,7 @@ SDK 不接管 CLI 进程、账号登录或远程平台鉴权;传入目录应
111
111
  "viewport": { "width": 393, "height": 852 },
112
112
  "sdkUrl": "/__miniapp_workbench__/sdk/workbench.js",
113
113
  "remote": {
114
- "serviceUrl": "https://miniapp-sandbox.beta.xiaohongshu.com/simulator/",
114
+ "serviceUrl": "https://miniapp-sandbox.xiaohongshu.com/simulator/",
115
115
  "appId": "local-workbench",
116
116
  "businessLine": "skill-designable",
117
117
  "tracker": false
@@ -184,14 +184,12 @@ Mock 只解析根目录、调用 `startWorkbenchServer()`、唤起浏览器并
184
184
  维护方执行:
185
185
 
186
186
  ```shell
187
- pnpm --filter @skillbuilder/miniapp-simulator test:package
188
187
  pnpm --dir packages/miniapp-simulator pack --pack-destination /absolute/path/to/output
189
188
  ```
190
189
 
191
190
  `prepack` 自动构建工作台、Node 入口、类型声明和兼容既有入口的 SW 产物,不上传 CDN 或执行 npm 发布。
192
- 包验证会在仓库外新建临时项目,只安装本次 tarball,运行依赖从 npm 官方源下载;不会通过 workspace link 或已有根目录依赖掩盖漏包问题。
193
- 覆盖导出文件、ESM / CommonJS 动态导入、无 DOM 的 Node 类型、浏览器类型、HTTP 资源、SSE 文件变更通知与退出清理。
194
- 测试所用的 TypeScript 编译器属于验证工具,不是外部 CLI 的运行依赖。临时目录和安装包路径会打印,便于复验。
191
+ 建议在仓库外新建空项目,只安装本次 tarball,运行依赖从 npm 官方源下载,避免 workspace link 或已有根目录依赖掩盖漏包问题。
192
+ 手动验收导出文件、ESM / CommonJS 动态导入、Node / 浏览器类型、HTTP 资源、SSE 文件变更通知与退出清理。
195
193
 
196
- **发布前仍需确认:**目标 registry、scope 发布权限、版本号及外发审批;远程 Simulator 默认是 Beta 地址,必须从实际外部网络验证可达性和访问策略。
194
+ **发布前仍需确认:**目标 registry、scope 发布权限、版本号及外发审批;远程 Simulator 默认是正式地址,仍须从实际外部网络验证可达性和访问策略。
197
195
  单包集成不等于离线运行:工作台仍需联网加载远程 Runtime / Service Worker。安装包测试通过,不代表公共源已经发布或外网环境已经验收。
@@ -23,17 +23,7 @@ CLI 使用 `workbench/server` 的 Node 入口即可启动完整工作台,不
23
23
  | Mock CLI 帮助不启动服务、未开放参数拒绝 | 通过 |
24
24
  | 安装后的 Mock CLI 在浏览器唤起真实 Remote Runtime,加载本地小工具并交互 | 通过;限本机当前网络 |
25
25
 
26
- 测试入口:
27
-
28
- ```shell
29
- pnpm --filter @skillbuilder/miniapp-simulator test:package
30
-
31
- # 可指定另一消费端 Node;打包工具仍使用仓库环境。
32
- MINIAPP_CONSUMER_NODE=/absolute/path/to/node20 \
33
- pnpm --filter @skillbuilder/miniapp-simulator test:package
34
- ```
35
-
36
- 测试会保留临时安装目录与 `.tgz` 并打印路径,便于复验。用户参数、SDK 参数和接入示例见 [外部 CLI 接入契约](./external-cli-workbench.md)。
26
+ 以上为历史验证记录。复验时,在仓库外新建空项目安装目标 tarball 或已发布版本,检查导出、类型、本地服务及文件监听。用户参数、SDK 参数和接入示例见 [外部 CLI 接入契约](./external-cli-workbench.md)。
37
27
 
38
28
  ## 外发前的剩余门禁
39
29
 
@@ -240,16 +240,17 @@ Runtime 在每次建立连接时使用以下上下文请求一次聚合配置:
240
240
  }
241
241
  ```
242
242
 
243
- `businessLine` 是薄 SDK 必填参数,没有默认值。Server 返回 `csp`、`performance`、`jsapi` 三个独立
243
+ `businessLine` 是薄 SDK 必填参数,没有默认值。Server 返回 `csp`、`workerCsp`、`performance`、`jsapi` 和 `packageLimits` 独立
244
244
  配置块;聚合只减少请求数量,不会把不同配置项合并成一个 Apollo Key:
245
245
 
246
246
  | Apollo Key | 聚合响应字段 | 解释方 |
247
247
  | --- | --- | --- |
248
248
  | `miniappSimulatorCspPolicies` | `csp` | Runtime CSP 转换逻辑 |
249
+ | `miniappSimulatorWorkerCspPolicies` | `workerCsp` | Worker 资源响应 CSP |
249
250
  | `miniappSimulatorPerformancePolicies` | `performance` | Runtime 资源保护逻辑 |
250
251
  | `miniappSimulatorJsApiPolicies` | `jsapi` | Runtime JSAPI 路由逻辑 |
251
252
 
252
- 三个 Apollo Key 都采用相同的规则数组结构:
253
+ 策略 Apollo Key 都采用相同的规则数组结构:
253
254
 
254
255
  ```json
255
256
  [
@@ -0,0 +1,62 @@
1
+ # 模拟器 Worker 接入
2
+
3
+ 模拟器和客户端使用同一份 `device-capabilities-seal.iife.min.js` 安全脚本文件。
4
+ 脚本在 miniapp-js-api 仓库的 miniapp-secure-runtime 中构建,包含设备封印、Worker 构造函数限制、
5
+ onTerminate/offTerminate 原型 patch、单实例配额和连续隐藏 30 秒回收。
6
+ 模拟器不依赖安全运行时 npm 包,也不导入其源码、函数或类型。
7
+
8
+ 客户端只维护 Worker 的 `enabled` 开关。
9
+ OffscreenCanvas、WebGL 和 transferable 使用原生行为,由业务 JS 检测、实测。
10
+
11
+ ## 本地联调
12
+
13
+ 先构建安全脚本,再将文件显式同步到模拟器:
14
+
15
+ ```bash
16
+ # miniapp-js-api 仓库
17
+ pnpm --filter @skillbuilder/miniapp-secure-runtime build
18
+ pnpm --filter @skillbuilder/miniapp-secure-runtime test:worker
19
+
20
+ # skill-designable-kit 仓库,参数为本次构建产物的绝对路径
21
+ pnpm --filter @skillbuilder/miniapp-simulator sync:secure-runtime -- /absolute/path/to/miniapp-js-api/packages/miniapp-secure-runtime/dist/device-capabilities-seal.iife.min.js
22
+ pnpm --filter @skillbuilder/miniapp-simulator test:worker
23
+ pnpm --filter @skillbuilder/miniapp-simulator build:sw:dev
24
+ pnpm --filter @skillbuilder/miniapp-simulator build:remote:dev
25
+ ```
26
+
27
+ 同步脚本校验文件语法、必要标记和 20,000 字节上限,按原字节复制到
28
+ `src/injection/modules/device-capabilities-seal.iife.min.js` 并输出 SHA-256。
29
+ 该文件随模拟器代码保存;模拟器构建只读取此本地文件,不需要访问另一仓库或安装安全运行时包。
30
+ 安全运行时变更后,显式重新构建、同步文件并验证模拟器。
31
+ 完整客户端配置与约束见 miniapp-secure-runtime 的 `docs/worker-runtime.md`。
32
+
33
+ ## 模拟器职责
34
+
35
+ `src/seal-config.ts` 由模拟器维护设备能力枚举、元数据和白名单配置。
36
+ `src/capabilities/security-head-snippet.ts` 的 `buildSealScriptSource` 负责宿主配置序列化与静态安全脚本拼装。
37
+ `src/worker/adapters/` 由模拟器维护以下内容:
38
+
39
+ - `protocol.ts` / `manifest.json`:宿主接入类型、协议常量和默认 Worker CSP。
40
+ - `csp-policy.ts`:Worker CSP 的校验、逐指令覆盖与默认策略回退。
41
+ - `package-policy.ts`:根据 Worker 开关和会话标识生成宿主配置。
42
+ - `service-worker.ts`:识别 Worker 请求,校验请求归属,将原始文件字节与独立 Worker CSP 返回。
43
+ - `mount-recovery.ts`:资源 Service Worker 重启后,通过可信宿主恢复包快照并校验请求所属会话。
44
+ - `page-lifecycle.ts`:将可信父窗口状态接入私有控制器。
45
+ - `host-lifecycle.ts`:监听 iframe、宿主 CSS 可见性,接入远程 SDK 私有 MessagePort。
46
+ - `scripts.ts`:将安全脚本返回的私有 Worker 控制器连接到模拟器生命周期适配脚本。
47
+
48
+ Apollo `miniappSimulatorWorkerCspPolicies` 的获取、运行配置下发、CSP 策略处理和响应头附加均属于模拟器链路。
49
+ Worker 用户代码原字节加载,不解析、拼接、包装或注入管理片段。
50
+
51
+ ## 配置与安装
52
+
53
+ 宿主通过只读 `__MINITOOL_WORKER_CONTEXT__` 提供 Worker 配置;设备白名单使用
54
+ `__MINI_APP_SEAL_CONFIG__.allow`。`__MINITOOL_WORKER_STATE__` 是运行时只读状态输出。
55
+ 模拟器只注册一次 security 注入,执行完整安全脚本后将私有控制器连接到 iframe/远程宿主生命周期。
56
+ 页面可见性与销毁监听由安全脚本完成。
57
+
58
+ ## 验证
59
+
60
+ 安全运行时维护构造函数、原型 patch、生命周期和产物体积测试;
61
+ 模拟器维护包资源隔离、本地 CSP 策略、原始字节响应及 Chromium 集成测试,覆盖真实 Worker、CSP 阻断、宿主可见性与资源 SW 重启。
62
+ Playground `/worker` 使用远程模拟器和手机壳,包含正常、关闭、旧端未接入三种模式、绘图/传输测试与 ZIP 导出。