@fps-games/editor-deployment 0.2.3-beta.8 → 0.2.3-beta.9

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.
package/public/index.html CHANGED
@@ -10,10 +10,10 @@
10
10
  html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; background: #101018; }
11
11
  #renderCanvas { width: 100%; height: 100%; display: block; outline: none; }
12
12
  </style>
13
- <script type="importmap">{"imports":{"@fps-games/editor/playable-sdk":"./assets/playable-sdk-BhG9wmjo.js","@babylonjs/core":"./assets/babylon-core-S0gBQJVe.js","@fps-games/editor-core/plugin-host":"./assets/editor-core-plugin-host-c2002442.js"}}</script>
14
- <script type="module" crossorigin src="./assets/index-DsjoVi0a.js"></script>
13
+ <script type="importmap">{"imports":{"@fps-games/editor/playable-sdk":"./assets/playable-sdk-zyPm5JCa.js","@babylonjs/core":"./assets/babylon-core-S0gBQJVe.js","@fps-games/editor-core/plugin-host":"./assets/editor-core-plugin-host-c2002442.js"}}</script>
14
+ <script type="module" crossorigin src="./assets/index-CdqIKk65.js"></script>
15
15
  <link rel="modulepreload" crossorigin href="./assets/dynamicTexture-Cuby-VOT.js">
16
- <link rel="modulepreload" crossorigin href="./assets/playable-standard-project-BMleU1jU.js">
16
+ <link rel="modulepreload" crossorigin href="./assets/playable-standard-project-BA5pc2TZ.js">
17
17
  </head>
18
18
  <body>
19
19
  <canvas id="renderCanvas"></canvas>
@@ -1 +1,4 @@
1
- (@vl�f��r���b[2�Y�D:b� #*,S�h��f�W3<>7վ�l�җ�=������E/�;�zSh��i͐=`�A;��Mud��P@�Q@�j�����������T����:͞�h�����Ztw6̘�܌tG��M�?jҗJ�A�kԂ@�+$r��HY%��_h5��y���#lx'�÷�l�bj�n��k�ݸ5O,�y��[�!���⅀K��d##�8Bɤ1q
1
+ (@vl�f��r��]�S٥��:��dDe*������j���ڗ��`^�ҺGQu�>C ��%"qGZo>z 0��l7h糷�n��
2
+ �3
3
+ �Rmts�hiMέ���AE��'�i��jG�_��բ�öaF|��}��@ޤ�!�[/��G�@�R�5hx�Z(v�DN[��Ur���ޒ�H׏G����_���)u�<���6uj4�6X�Q��[�WPm;��4�$�h�Q *����_��W�0�|���l�ի��&)��x���8n��]O��Ϝ��)��Dn�%eQ,b(��<ϦJ"�^��@
4
+ $��v�ͅiu zQ?���@;�H/�����T��=:���}�ѻo<,wlj� $Πx�F����]�k;�
Binary file
@@ -1,5 +1,5 @@
1
- import{readFile as rt}from"node:fs/promises";import we from"node:path";var N="/__fps_editor_agent/v1",Z="/__fps_editor_agent/v1/asset-source-grants";var it=8*1024*1024;var at=256*1024;import{createHash as dt,randomBytes as q,randomUUID as fe}from"node:crypto";import{once as ce}from"node:events";import{createReadStream as xe}from"node:fs";import{mkdir as me,open as de,readFile as G,realpath as Le,rename as je,rm as W,stat as te,writeFile as Ue}from"node:fs/promises";import{createServer as Ge}from"node:http";import{spawn as $e}from"node:child_process";import f from"node:path";async function Ee(e){let n=await Le(e.projectRoot),i=e.host?.trim()||"127.0.0.1",a=qe(e.port??3012),c=f.resolve(n,e.workerEntry);if(Ie(n,c,"worker entry"),!(await te(c).catch(()=>null))?.isFile())throw new Error(`Editor worker entry is missing: ${c}`);let m=Ve(e.projectDefinition),S=Me(e.pluginSnapshots),l=new Map(e.builds.map(t=>[t.release,Fe(n,t)]));if(l.size===0||l.size!==e.builds.length)throw new Error("Editor Server requires uniquely named built Editor releases.");let p=e.activeRelease?.trim()||e.builds[0].release;if(!l.has(p))throw new Error(`Unknown active Editor release: ${p}`);let w=e.logger??console,$=fe(),ne=new Date().toISOString(),B=q(32).toString("base64url"),J=q(32).toString("base64url"),Re=await Be(f.resolve(n,e.runtimeBridgeTokenPath??".fps-editor/state/runtime-bridge-token")),z=e.testControl?e.controlToken?.trim()||q(32).toString("base64url"):null,oe=e.runtimeUrl?.trim()||"http://127.0.0.1:3011",M=f.join(n,".fps-editor/state/agent-runtime.json"),K=`${M}.owner`,ie=await Je(K,$),Se=ee(e.workerReadyTimeoutMs,3e4),ae=ee(e.workerHeartbeatTimeoutMs,1e4),Oe=ee(e.workerRestartLimit,3),I=null,D=new WeakSet,C=!1,_="starting",b=0,O=null,y=null,H=0,k=null,Y,x=null,P=null,L=null,A=Ge((t,r)=>{ve(t,r).catch(o=>{w.error?.(`[fps-editor-server] request failed: ${U(o)}`),r.headersSent?r.destroy(o instanceof Error?o:void 0):u(r,500,{ok:!1,error:U(o)})})});A.on("error",t=>w.error?.(`[fps-editor-server] ${U(t)}`));try{await new Promise((t,r)=>{A.once("error",r),A.listen(a,i,()=>{A.off("error",r),t()})}),await X()}catch(t){throw await _e(A).catch(()=>{}),await ue(K,ie).catch(()=>{}),t}x=setInterval(()=>{C||_!=="ready"||!k||!I||Date.now()-Date.parse(k)<=ae||(w.warn?.("[fps-editor-server] Editor worker heartbeat timed out; restarting worker."),_="restarting",I.kill("SIGKILL"))},Math.min(1e3,Math.max(100,Math.floor(ae/3)))),x.unref?.();let se=`http://${i}:${a}`;w.info?.(`[fps-editor-server] ready at ${se}`);function F(){let t=l.get(p);return{ok:_==="ready",serverInstanceId:$,pid:process.pid,startedAt:ne,host:i,port:a,worker:{status:_,generation:b,pid:I?.pid??null,serviceInstanceId:O,restartCount:H,lastHeartbeatAt:k,...Y?{lastExit:Y}:{}},editor:{activeRelease:p,artifactId:t.artifactId,build:t.build}}}async function X(){if(C)return;b+=1,_=b===1?"starting":"restarting",O=null,y=null,k=null;let t=l.get(p),r=$e(process.execPath,[c],{cwd:n,stdio:["ignore","inherit","inherit","ipc"],env:{...process.env,FPS_EDITOR_WORKER_AGENT_TOKEN:B,FPS_EDITOR_WORKER_BROWSER_TOKEN:J,FPS_EDITOR_WORKER_RUNTIME_DEBUG_TOKEN:Re,FPS_EDITOR_WORKER_RUNTIME_URL:oe,FPS_EDITOR_WORKER_BUILD:JSON.stringify(t.build),FPS_EDITOR_WORKER_GENERATION:String(b),FPS_EDITOR_WORKER_PROJECT_DEFINITION:JSON.stringify(m),FPS_EDITOR_WORKER_PLUGIN_SNAPSHOTS:JSON.stringify(S),...e.workerJournalPath?.trim()?{FPS_EDITOR_WORKER_JOURNAL_PATH:e.workerJournalPath.trim()}:{}}});I=r,r.on("message",s=>{ge(r,s).catch(d=>{w.error?.(`[fps-editor-server] invalid worker message: ${U(d)}`),r.kill("SIGKILL")})}),r.once("exit",(s,d)=>{ye(r,s,d)});let o=Date.now()+Se;for(;!C&&I===r&&_!=="ready"&&Date.now()<o;)await new Promise(s=>setTimeout(s,25));if(_!=="ready")throw D.add(r),r.kill("SIGKILL"),new Error("Editor Service worker did not become ready before the startup deadline.")}async function ge(t,r){if(t!==I||!Ye(r))return;if(r.type==="heartbeat"){r.serviceInstanceId===O&&(k=new Date().toISOString());return}let o=new URL(r.internalUrl);if(o.protocol!=="http:"||o.hostname!=="127.0.0.1")throw new Error("Editor worker internal URL must use loopback HTTP.");O=r.serviceInstanceId,y=o.toString(),k=new Date().toISOString(),_="ready",L={schemaVersion:1,protocolVersion:2,canonicalProjectRoot:n,serviceInstanceId:r.serviceInstanceId,pid:process.pid,endpoint:`${et(i,a)}${N}`,startedAt:ne,packageName:"@fps-games/editor",packageVersion:l.get(p).build.version,buildKey:l.get(p).build.buildId,token:B,tokenScopes:["read","write"]},await Ke(M,L)}async function ye(t,r,o){let s=D.has(t);if(D.delete(t),I===t&&(I=null),Y={code:r,signal:o,at:new Date().toISOString()},y=null,O=null,await pe(M,L),L=null,C||s){_="stopped";return}if(H+=1,H>Oe){_="fused",w.error?.("[fps-editor-server] Editor worker restart fuse opened.");return}_="restarting";let d=[100,500,2e3][Math.min(H-1,2)];P=setTimeout(()=>{P=null,X().catch(h=>{w.error?.(`[fps-editor-server] worker restart failed: ${U(h)}`)})},d),P.unref?.()}async function ve(t,r){let o=new URL(t.url??"/",`http://${i}:${a}`);if(Ze(r),t.method==="GET"&&o.pathname==="/__fps_editor/health"){u(r,_==="ready"?200:503,F());return}if(t.method==="GET"&&o.pathname==="/__fps_editor/status"){u(r,200,F());return}if(t.method==="GET"&&o.pathname==="/__fps_editor/bootstrap.json"){if(!O){u(r,503,{ok:!1,error:"editor_worker_unavailable"});return}let s=l.get(p);u(r,200,{ok:!0,serverInstanceId:$,workerGeneration:b,runtime:{protocolVersion:2,serviceInstanceId:O,gatewayPath:N,token:J},build:s.build});return}if((t.method==="GET"||t.method==="HEAD")&&(o.pathname==="/"||o.pathname==="/index.html"||o.pathname==="/__fps_editor/document")){await be(r,o.searchParams.get("instanceId"),o.searchParams.get("release"),t.method==="HEAD");return}if((t.method==="GET"||t.method==="HEAD")&&o.pathname.startsWith("/__fps_editor/build/")){await ke(t,r,o);return}if((t.method==="GET"||t.method==="HEAD")&&o.pathname.startsWith("/src/")){await Pe(t,r,o);return}if(t.method==="POST"&&o.pathname.startsWith("/__fps_editor/control/")){await Ne(t,r,o.pathname);return}if(o.pathname===N||o.pathname===Z||o.pathname.startsWith("/__fps_editor_authoring")||o.pathname.startsWith("/__fps_editor/assets/")){await Ae(t,r,o);return}u(r,404,{ok:!1,error:"not_found"})}async function be(t,r,o,s=!1){if(!O){u(t,503,{ok:!1,error:"editor_worker_unavailable"});return}let d=l.get(p);if(o?.trim()&&o.trim()!==p){u(t,409,{ok:!1,error:"editor_build_incompatible",activeRelease:p,requestedRelease:o.trim(),activeBuild:d.build});return}let h=await G(f.join(d.rootPath,"index.html"),"utf8"),R=`/__fps_editor/build/${encodeURIComponent(d.artifactId)}/`,T=[`<base href="${tt(R)}">`,"<script>","window.__FPS_EDITOR_HOSTED_SANDBOX__=true;",'window.__BOOT_MODE="edit";',`window.__FPS_EDITOR_AGENT_RUNTIME__=${j({protocolVersion:2,serviceInstanceId:O,gatewayPath:N,token:J})};`,`window.__FPS_EDITOR_SERVER__=${j({serverInstanceId:$,workerGeneration:b,instanceId:r?.trim()||null,release:d.release,artifactId:d.artifactId,build:d.build})};`,`window.__FPS_EDITOR_PROJECT_DEFINITION__=${j(m)};`,`window.__FPS_EDITOR_PLUGIN_SNAPSHOTS__=${j(S)};`,`window.__FPS_EDITOR_RUNTIME_URL__=${j(oe)};`,"window.__FPS_EDITOR_SERVER_MONITOR__=setInterval(async()=>{",'try{const response=await fetch("/__fps_editor/bootstrap.json",{cache:"no-store"});',"if(!response.ok)return;const next=await response.json();","if(next.serverInstanceId!==window.__FPS_EDITOR_SERVER__.serverInstanceId","||next.workerGeneration!==window.__FPS_EDITOR_SERVER__.workerGeneration){",'const url=new URL(location.href);url.searchParams.set("__fpsWorkerGeneration",String(next.workerGeneration));','url.searchParams.set("__fpsServerInstance",String(next.serverInstanceId));',"location.replace(url);}","}catch{}","},1000);",'addEventListener("pagehide",()=>clearInterval(window.__FPS_EDITOR_SERVER_MONITOR__),{once:true});',"</script>"].join(""),v=/<head[^>]*>/i.test(h)?h.replace(/<head[^>]*>/i,Ce=>`${Ce}${T}`):`${T}${h}`;t.statusCode=200,t.setHeader("Cache-Control","no-store"),t.setHeader("Content-Type","text/html; charset=utf-8"),t.end(s?void 0:v)}async function ke(t,r,o){let s=o.pathname.split("/").filter(Boolean),d=decodeURIComponent(s[2]??""),h=[...l.values()].find(T=>T.artifactId===d);if(!h){u(r,404,{ok:!1,error:"editor_build_not_found"});return}let R=s.slice(3).map(decodeURIComponent).join("/");await le(t,r,h.rootPath,R,!0)}async function Pe(t,r,o){let s=decodeURIComponent(o.pathname.slice(1));await le(t,r,n,s,!1,o.searchParams.has("v"))}async function Ae(t,r,o){if(!y||_!=="ready"){u(r,503,{ok:!1,error:"editor_worker_unavailable"});return}let s=o.pathname===Z&&t.method==="POST",d=t.method==="GET"||t.method==="HEAD"||s?void 0:await Xe(t,32*1024*1024),h={};for(let T of["accept","authorization","content-type","if-none-match","range"]){let v=t.headers[T];typeof v=="string"&&(h[T]=v)}let R=await fetch(new URL(`${o.pathname}${o.search}`,y),{method:t.method,headers:h,...s?{body:t,duplex:"half"}:d?{body:new Uint8Array(d)}:{}});r.statusCode=R.status;for(let T of["content-type","content-length","etag","content-range","accept-ranges"]){let v=R.headers.get(T);v&&r.setHeader(T,v)}r.setHeader("Cache-Control","no-store"),r.end(t.method==="HEAD"?void 0:Buffer.from(await R.arrayBuffer()))}async function Ne(t,r,o){if(!z||t.headers["x-fps-editor-control-token"]!==z){u(r,404,{ok:!1,error:"not_found"});return}if(o==="/__fps_editor/control/worker/crash"){if(!I){u(r,409,{ok:!1,error:"worker_unavailable"});return}I.kill("SIGKILL"),u(r,202,{ok:!0});return}let s=decodeURIComponent(o.slice(31));if(o.startsWith("/__fps_editor/control/activate/")&&l.has(s)){let d=await De();if(d.dirty){u(r,409,{ok:!1,error:"editor_document_dirty",documentRevision:d.documentRevision,activeRelease:p,requestedRelease:s});return}let h=p;if(p=s,H=0,I){let R=I;_="restarting",D.add(R);let T=ce(R,"exit");R.kill("SIGTERM"),await T}await X(),u(r,200,{ok:!0,previousRelease:h,activeRelease:p,status:F()});return}u(r,404,{ok:!1,error:"control_not_found"})}async function De(){if(!y||_!=="ready")throw new Error("Editor worker is unavailable for release compatibility checks.");let t=await fetch(new URL(N,y),{method:"POST",headers:{authorization:`Bearer ${B}`,"content-type":"application/json"},body:JSON.stringify({kind:"status"})}),r=await t.json();if(!t.ok||r.ok!==!0||r.kind!=="status"||typeof r.status?.dirty!="boolean"||!Number.isSafeInteger(r.status.documentRevision))throw new Error("Editor worker returned an invalid status during release compatibility checks.");return{dirty:r.status.dirty,documentRevision:r.status.documentRevision}}let V=null;return Object.freeze({url:se,controlToken:z,status:F,dispose(){return V||(V=(async()=>{C=!0,x&&clearInterval(x),P&&clearTimeout(P),x=null,P=null;let t=I;if(t){D.add(t);let r=ce(t,"exit").catch(()=>[]);t.kill("SIGTERM"),await Promise.race([r,new Promise(o=>setTimeout(o,5e3))]),t.exitCode===null&&t.signalCode===null&&t.kill("SIGKILL")}await _e(A),await pe(M,L),await ue(K,ie)})(),V)}})}function Me(e){if(e===void 0)return Object.freeze({});if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Editor Server Plugin snapshots must be an object.");return Object.freeze(structuredClone(e))}function Fe(e,n){let i=f.resolve(e,n.rootPath);if(Ie(e,i,"built Editor root"),!n.release.trim()||!n.artifactId.trim())throw new Error("Editor build identity is incomplete.");return Object.freeze({...n,rootPath:i,build:Object.freeze({...n.build})})}function Ve(e){if(!e||typeof e!="object"||Array.isArray(e)||e.schemaVersion!==1||typeof e.projectId!="string"||typeof e.projectName!="string"||!e.authoring||!Array.isArray(e.plugins))throw new Error("Editor Server Project definition is invalid.");return structuredClone(e)}async function le(e,n,i,a,c,E=!1){if(!a||a.includes("\\")||a.split("/").includes("..")){u(n,404,{ok:!1,error:"file_not_found"});return}let m=f.resolve(i,a),S=f.relative(i,m);if(!S||S===".."||S.startsWith(`..${f.sep}`)||f.isAbsolute(S)){u(n,404,{ok:!1,error:"file_not_found"});return}let l=await te(m).catch(()=>null);if(!l?.isFile()){u(n,404,{ok:!1,error:"file_not_found"});return}let p=e.headers["accept-encoding"]??"",w=await We(m,p);n.statusCode=200,n.setHeader("Cache-Control",c||E?"public, max-age=31536000, immutable":"no-cache"),n.setHeader("Content-Type",Qe(m)),n.setHeader("Content-Length",String(w?.stat.size??l.size)),w&&(n.setHeader("Content-Encoding",w.encoding),n.setHeader("Vary","Accept-Encoding")),e.method==="HEAD"?n.end():xe(w?.path??m).pipe(n)}async function We(e,n){let i=n.toLowerCase().split(",").map(c=>c.trim()),a=c=>i.some(E=>{let[m,...S]=E.split(";").map(l=>l.trim());return m!==c&&m!=="*"?!1:!S.some(l=>l==="q=0"||l==="q=0.0")});for(let c of a("br")?[{suffix:".br",encoding:"br"},{suffix:".gz",encoding:"gzip"}]:[{suffix:".gz",encoding:"gzip"}]){if(c.encoding==="gzip"&&!a("gzip"))continue;let E=`${e}${c.suffix}`,m=await te(E).catch(()=>null);if(m?.isFile())return{path:E,encoding:c.encoding,stat:m}}return null}async function Be(e){let n=(await G(e,"utf8")).trim();if(!n)throw new Error(`Editor Server secret is empty: ${e}`);return n}async function Je(e,n){await me(f.dirname(e),{recursive:!0,mode:448});let i={pid:process.pid,serverInstanceId:n,createdAt:new Date().toISOString()};try{let a=await de(e,"wx",384);return await a.writeFile(`${JSON.stringify(i)}
2
- `,"utf8"),await a.close(),i}catch{let c=await G(e,"utf8").then(JSON.parse).catch(()=>null);if(typeof c?.pid=="number"&&ze(c.pid))throw new Error(`Editor Server project lock is already owned by PID ${c.pid}.`);await W(e,{force:!0});let E=await de(e,"wx",384);return await E.writeFile(`${JSON.stringify(i)}
3
- `,"utf8"),await E.close(),i}}async function ue(e,n){(await G(e,"utf8").then(JSON.parse).catch(()=>null))?.serverInstanceId===n.serverInstanceId&&await W(e,{force:!0})}function ze(e){try{return process.kill(e,0),!0}catch{return!1}}async function Ke(e,n){await me(f.dirname(e),{recursive:!0,mode:448});let i=`${e}.${process.pid}.${fe()}.tmp`;try{await Ue(i,`${JSON.stringify(n,null,2)}
4
- `,{encoding:"utf8",mode:384}),await je(i,e)}finally{await W(i,{force:!0}).catch(()=>{})}}async function pe(e,n){if(!n)return;let i=await G(e,"utf8").then(JSON.parse).catch(()=>null);i?.serviceInstanceId===n.serviceInstanceId&&i.pid===n.pid&&await W(e,{force:!0})}function Ye(e){if(!e||typeof e!="object")return!1;let n=e;return n.type==="heartbeat"?typeof n.serviceInstanceId=="string":n.type==="ready"&&typeof n.serviceInstanceId=="string"&&typeof n.internalUrl=="string"}async function Xe(e,n){let i=[],a=0;for await(let c of e){let E=Buffer.isBuffer(c)?c:Buffer.from(c);if(a+=E.byteLength,a>n)throw new Error("Editor Server request body is too large.");i.push(E)}return Buffer.concat(i)}function Ze(e){e.setHeader("Access-Control-Allow-Origin","*"),e.setHeader("Cross-Origin-Resource-Policy","cross-origin"),e.setHeader("Cross-Origin-Embedder-Policy","credentialless"),e.setHeader("X-Content-Type-Options","nosniff")}function u(e,n,i){e.statusCode=n,e.setHeader("Cache-Control","no-store"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.end(`${JSON.stringify(i)}
5
- `)}function Qe(e){switch(f.extname(e).toLowerCase()){case".js":case".mjs":return"text/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".json":return"application/json; charset=utf-8";case".html":return"text/html; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".webp":return"image/webp";case".glb":return"model/gltf-binary";case".gltf":return"model/gltf+json";default:return"application/octet-stream"}}function Ie(e,n,i){let a=f.relative(e,n);if(!a||a===".."||a.startsWith(`..${f.sep}`)||f.isAbsolute(a))throw new Error(`Editor Server ${i} must be inside the canonical project root.`)}function qe(e){if(!Number.isSafeInteger(e)||e<1||e>65535)throw new Error("Editor Server port is invalid.");return e}function ee(e,n){return Number.isSafeInteger(e)&&e>0?e:n}function et(e,n){return`http://${e==="0.0.0.0"?"127.0.0.1":e}:${n}`}function j(e){return JSON.stringify(e).replace(/</g,"\\u003c").replace(/>/g,"\\u003e")}function tt(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;")}async function _e(e){e.listening&&await new Promise((n,i)=>e.close(a=>a?i(a):n()))}function U(e){return e instanceof Error?e.message:String(e)}nt();var Te=we.resolve(process.cwd()),g=JSON.parse(await rt(we.join(Te,".fps-editor/current.json"),"utf8").catch(e=>{throw new Error("Editor deployment is not prepared. Run npm run prepare:server first.",{cause:e})}));if(g.schemaVersion!==3||typeof g.workerEntry!="string"||!g.projectDefinition||!Array.isArray(g.builds))throw new Error("Editor deployment state is invalid. Run npm run prepare:server again.");var re=await Ee({projectRoot:Te,workerEntry:g.workerEntry,projectDefinition:g.projectDefinition,pluginSnapshots:g.pluginSnapshots,builds:g.builds,activeRelease:process.env.FPS_EDITOR_RELEASE?.trim()||"a",host:process.env.FPS_EDITOR_SERVER_HOST?.trim()||"127.0.0.1",port:ot(process.env.FPS_EDITOR_SERVER_PORT,3012),runtimeUrl:process.env.FPS_EDITOR_RUNTIME_URL?.trim()||"http://127.0.0.1:3011",runtimeBridgeTokenPath:".fps-editor/state/runtime-bridge-token",workerJournalPath:process.env.FPS_EDITOR_SERVER_JOURNAL_PATH,workerReadyTimeoutMs:6e4,workerHeartbeatTimeoutMs:1e4,workerRestartLimit:3,testControl:process.env.FPS_EDITOR_SERVER_TEST_CONTROL==="true",controlToken:process.env.FPS_EDITOR_SERVER_CONTROL_TOKEN});re.controlToken&&console.log(`[fps-editor-server] test control token: ${re.controlToken}`);var he=!1;for(let e of["SIGINT","SIGTERM"])process.once(e,()=>{he||(he=!0,re.dispose().finally(()=>{process.exitCode=e==="SIGINT"?130:143}))});function nt(){let e=Number.parseInt(process.versions.node.split(".")[0]??"",10);if(!Number.isSafeInteger(e)||e<24||e>=27)throw new Error(`fps-editor-server requires Node.js >=24 <27; current version is ${process.versions.node}.`)}function ot(e,n){if(!e?.trim())return n;let i=Number(e);if(!Number.isSafeInteger(i)||i<1||i>65535)throw new Error(`FPS_EDITOR_SERVER_PORT must be a valid port, got ${e}.`);return i}
1
+ import{readFile as ot}from"node:fs/promises";import he from"node:path";var D="/__fps_editor_agent/v1",Q="/__fps_editor_agent/v1/asset-source-grants";var st=32*1024;var ct=8*1024*1024;var dt=256*1024;import{createHash as Le,randomBytes as ee,randomUUID as me}from"node:crypto";import{once as de}from"node:events";import{createReadStream as je}from"node:fs";import{mkdir as Ee,open as le,readFile as M,realpath as Ue,rename as Ge,rm as B,stat as re,writeFile as Me}from"node:fs/promises";import{createServer as $e}from"node:http";import{spawn as Fe}from"node:child_process";import f from"node:path";async function Ie(e){let n=await Ue(e.projectRoot),i=e.projectEpoch?.trim()||Le("sha256").update(n).digest("hex").slice(0,32),a=e.host?.trim()||"127.0.0.1",s=tt(e.port??3012),_=f.resolve(n,e.workerEntry);if(Te(n,_,"worker entry"),!(await re(_).catch(()=>null))?.isFile())throw new Error(`Editor worker entry is missing: ${_}`);let w=Be(e.projectDefinition),S=Ve(e.pluginSnapshots),m=new Map(e.builds.map(t=>[t.release,We(n,t)]));if(m.size===0||m.size!==e.builds.length)throw new Error("Editor Server requires uniquely named built Editor releases.");let d=e.activeRelease?.trim()||e.builds[0].release;if(!m.has(d))throw new Error(`Unknown active Editor release: ${d}`);let y=e.logger??console,$=me(),oe=new Date().toISOString(),J=ee(32).toString("base64url"),z=ee(32).toString("base64url"),Se=await ze(f.resolve(n,e.runtimeBridgeTokenPath??".fps-editor/state/runtime-bridge-token")),K=e.testControl?e.controlToken?.trim()||ee(32).toString("base64url"):null,ie=e.runtimeUrl?.trim()||"http://127.0.0.1:3011",F=f.join(n,".fps-editor/state/agent-runtime.json"),Y=`${F}.owner`,ae=await Ke(Y,$),Oe=te(e.workerReadyTimeoutMs,3e4),se=te(e.workerHeartbeatTimeoutMs,1e4),ge=te(e.workerRestartLimit,3),E=null,H=new WeakSet,C=!1,p="starting",P=0,O=null,A=null,x=0,b=null,X,L=null,k=null,j=null,N=$e((t,r)=>{ve(t,r).catch(o=>{y.error?.(`[fps-editor-server] request failed: ${G(o)}`),r.headersSent?r.destroy(o instanceof Error?o:void 0):u(r,500,{ok:!1,error:G(o)})})});N.on("error",t=>y.error?.(`[fps-editor-server] ${G(t)}`));try{await new Promise((t,r)=>{N.once("error",r),N.listen(s,a,()=>{N.off("error",r),t()})}),await Z()}catch(t){throw await fe(N).catch(()=>{}),await _e(Y,ae).catch(()=>{}),t}L=setInterval(()=>{C||p!=="ready"||!b||!E||Date.now()-Date.parse(b)<=se||(y.warn?.("[fps-editor-server] Editor worker heartbeat timed out; restarting worker."),p="restarting",E.kill("SIGKILL"))},Math.min(1e3,Math.max(100,Math.floor(se/3)))),L.unref?.();let ce=`http://${a}:${s}`;y.info?.(`[fps-editor-server] ready at ${ce}`);function V(){let t=m.get(d);return{ok:p==="ready",serverInstanceId:$,pid:process.pid,startedAt:oe,projectEpoch:i,host:a,port:s,worker:{status:p,generation:P,pid:E?.pid??null,serviceInstanceId:O,restartCount:x,lastHeartbeatAt:b,...X?{lastExit:X}:{}},editor:{activeRelease:d,artifactId:t.artifactId,build:t.build}}}async function Z(){if(C)return;P+=1,p=P===1?"starting":"restarting",O=null,A=null,b=null;let t=m.get(d),r=Fe(process.execPath,[_],{cwd:n,stdio:["ignore","inherit","inherit","ipc"],env:{...process.env,FPS_EDITOR_WORKER_AGENT_TOKEN:J,FPS_EDITOR_WORKER_BROWSER_TOKEN:z,FPS_EDITOR_WORKER_RUNTIME_DEBUG_TOKEN:Se,FPS_EDITOR_WORKER_RUNTIME_URL:ie,FPS_EDITOR_WORKER_BUILD:JSON.stringify(t.build),FPS_EDITOR_WORKER_GENERATION:String(P),FPS_EDITOR_WORKER_PROJECT_DEFINITION:JSON.stringify(w),FPS_EDITOR_WORKER_PLUGIN_SNAPSHOTS:JSON.stringify(S),...e.workerJournalPath?.trim()?{FPS_EDITOR_WORKER_JOURNAL_PATH:e.workerJournalPath.trim()}:{}}});E=r,r.on("message",c=>{ye(r,c).catch(l=>{y.error?.(`[fps-editor-server] invalid worker message: ${G(l)}`),r.kill("SIGKILL")})}),r.once("exit",(c,l)=>{Ae(r,c,l)});let o=Date.now()+Oe;for(;!C&&E===r&&p!=="ready"&&Date.now()<o;)await new Promise(c=>setTimeout(c,25));if(p!=="ready")throw H.add(r),r.kill("SIGKILL"),new Error("Editor Service worker did not become ready before the startup deadline.")}async function ye(t,r){if(t!==E||!Ze(r))return;if(r.type==="heartbeat"){r.serviceInstanceId===O&&(b=new Date().toISOString());return}let o=new URL(r.internalUrl);if(o.protocol!=="http:"||o.hostname!=="127.0.0.1")throw new Error("Editor worker internal URL must use loopback HTTP.");O=r.serviceInstanceId,A=o.toString(),b=new Date().toISOString(),p="ready",j={schemaVersion:1,protocolVersion:2,canonicalProjectRoot:n,serviceInstanceId:r.serviceInstanceId,pid:process.pid,endpoint:`${rt(a,s)}${D}`,startedAt:oe,packageName:"@fps-games/editor",packageVersion:m.get(d).build.version,buildKey:m.get(d).build.buildId,token:J,tokenScopes:["read","write"]},await Xe(F,j)}async function Ae(t,r,o){let c=H.has(t);if(H.delete(t),E===t&&(E=null),X={code:r,signal:o,at:new Date().toISOString()},A=null,O=null,await pe(F,j),j=null,C||c){p="stopped";return}if(x+=1,x>ge){p="fused",y.error?.("[fps-editor-server] Editor worker restart fuse opened.");return}p="restarting";let l=[100,500,2e3][Math.min(x-1,2)];k=setTimeout(()=>{k=null,Z().catch(I=>{y.error?.(`[fps-editor-server] worker restart failed: ${G(I)}`)})},l),k.unref?.()}async function ve(t,r){let o=new URL(t.url??"/",`http://${a}:${s}`);if(qe(r),t.method==="GET"&&o.pathname==="/__fps_editor/health"){u(r,p==="ready"?200:503,V());return}if(t.method==="GET"&&o.pathname==="/__fps_editor/status"){u(r,200,V());return}if(t.method==="GET"&&o.pathname==="/__fps_editor/bootstrap.json"){if(!O){u(r,503,{ok:!1,error:"editor_worker_unavailable"});return}let c=m.get(d);u(r,200,{ok:!0,serverInstanceId:$,projectEpoch:i,workerGeneration:P,projectDefinition:w,runtime:{protocolVersion:2,serviceInstanceId:O,gatewayPath:D,token:z},build:c.build});return}if((t.method==="GET"||t.method==="HEAD")&&(o.pathname==="/"||o.pathname==="/index.html"||o.pathname==="/__fps_editor/document")){await Pe(r,o.searchParams.get("instanceId"),o.searchParams.get("release"),t.method==="HEAD");return}if((t.method==="GET"||t.method==="HEAD")&&o.pathname.startsWith("/__fps_editor/build/")){await be(t,r,o);return}if((t.method==="GET"||t.method==="HEAD")&&o.pathname.startsWith("/src/")){await ke(t,r,o);return}if(t.method==="POST"&&o.pathname.startsWith("/__fps_editor/control/")){await De(t,r,o.pathname);return}if(o.pathname===D||o.pathname===Q||o.pathname.startsWith("/__fps_editor_authoring")||o.pathname.startsWith("/__fps_editor/assets/")){await Ne(t,r,o);return}u(r,404,{ok:!1,error:"not_found"})}async function Pe(t,r,o,c=!1){if(!O){u(t,503,{ok:!1,error:"editor_worker_unavailable"});return}let l=m.get(d);if(o?.trim()&&o.trim()!==d){u(t,409,{ok:!1,error:"editor_build_incompatible",activeRelease:d,requestedRelease:o.trim(),activeBuild:l.build});return}let I=await M(f.join(l.rootPath,"index.html"),"utf8"),h=`/__fps_editor/build/${encodeURIComponent(l.artifactId)}/`,R=[`<base href="${nt(h)}">`,"<script>","window.__FPS_EDITOR_HOSTED_SANDBOX__=true;",'window.__BOOT_MODE="edit";',`window.__FPS_EDITOR_AGENT_RUNTIME__=${U({protocolVersion:2,serviceInstanceId:O,gatewayPath:D,token:z})};`,`window.__FPS_EDITOR_SERVER__=${U({serverInstanceId:$,workerGeneration:P,instanceId:r?.trim()||null,release:l.release,artifactId:l.artifactId,build:l.build})};`,`window.__FPS_EDITOR_PROJECT_DEFINITION__=${U(w)};`,`window.__FPS_EDITOR_PLUGIN_SNAPSHOTS__=${U(S)};`,`window.__FPS_EDITOR_RUNTIME_URL__=${U(ie)};`,"window.__FPS_EDITOR_SERVER_MONITOR__=setInterval(async()=>{",'try{const response=await fetch("/__fps_editor/bootstrap.json",{cache:"no-store"});',"if(!response.ok)return;const next=await response.json();","if(next.serverInstanceId!==window.__FPS_EDITOR_SERVER__.serverInstanceId","||next.workerGeneration!==window.__FPS_EDITOR_SERVER__.workerGeneration){",'const url=new URL(location.href);url.searchParams.set("__fpsWorkerGeneration",String(next.workerGeneration));','url.searchParams.set("__fpsServerInstance",String(next.serverInstanceId));',"location.replace(url);}","}catch{}","},1000);",'addEventListener("pagehide",()=>clearInterval(window.__FPS_EDITOR_SERVER_MONITOR__),{once:true});',"</script>"].join(""),v=/<head[^>]*>/i.test(I)?I.replace(/<head[^>]*>/i,Ce=>`${Ce}${R}`):`${R}${I}`;t.statusCode=200,t.setHeader("Cache-Control","no-store"),t.setHeader("Content-Type","text/html; charset=utf-8"),t.end(c?void 0:v)}async function be(t,r,o){let c=o.pathname.split("/").filter(Boolean),l=decodeURIComponent(c[2]??""),I=[...m.values()].find(R=>R.artifactId===l);if(!I){u(r,404,{ok:!1,error:"editor_build_not_found"});return}let h=c.slice(3).map(decodeURIComponent).join("/");await ue(t,r,I.rootPath,h,!0)}async function ke(t,r,o){let c=decodeURIComponent(o.pathname.slice(1));await ue(t,r,n,c,!1,o.searchParams.has("v"))}async function Ne(t,r,o){if(!A||p!=="ready"){u(r,503,{ok:!1,error:"editor_worker_unavailable"});return}let c=o.pathname===Q&&t.method==="POST",l=t.method==="GET"||t.method==="HEAD"||c?void 0:await Qe(t,32*1024*1024),I={};for(let R of["accept","authorization","content-type","if-none-match","range"]){let v=t.headers[R];typeof v=="string"&&(I[R]=v)}let h=await fetch(new URL(`${o.pathname}${o.search}`,A),{method:t.method,headers:I,...c?{body:t,duplex:"half"}:l?{body:new Uint8Array(l)}:{}});r.statusCode=h.status;for(let R of["content-type","content-length","etag","content-range","accept-ranges","server-timing"]){let v=h.headers.get(R);v&&r.setHeader(R,v)}r.setHeader("Cache-Control","no-store"),r.end(t.method==="HEAD"?void 0:Buffer.from(await h.arrayBuffer()))}async function De(t,r,o){if(!K||t.headers["x-fps-editor-control-token"]!==K){u(r,404,{ok:!1,error:"not_found"});return}if(o==="/__fps_editor/control/worker/crash"){if(!E){u(r,409,{ok:!1,error:"worker_unavailable"});return}E.kill("SIGKILL"),u(r,202,{ok:!0});return}let c=decodeURIComponent(o.slice(31));if(o.startsWith("/__fps_editor/control/activate/")&&m.has(c)){let l=await He();if(l.dirty){u(r,409,{ok:!1,error:"editor_document_dirty",documentRevision:l.documentRevision,activeRelease:d,requestedRelease:c});return}let I=d;if(d=c,x=0,E){let h=E;p="restarting",H.add(h);let R=de(h,"exit");h.kill("SIGTERM"),await R}await Z(),u(r,200,{ok:!0,previousRelease:I,activeRelease:d,status:V()});return}u(r,404,{ok:!1,error:"control_not_found"})}async function He(){if(!A||p!=="ready")throw new Error("Editor worker is unavailable for release compatibility checks.");let t=await fetch(new URL(D,A),{method:"POST",headers:{authorization:`Bearer ${J}`,"content-type":"application/json"},body:JSON.stringify({kind:"status"})}),r=await t.json();if(!t.ok||r.ok!==!0||r.kind!=="status"||typeof r.status?.dirty!="boolean"||!Number.isSafeInteger(r.status.documentRevision))throw new Error("Editor worker returned an invalid status during release compatibility checks.");return{dirty:r.status.dirty,documentRevision:r.status.documentRevision}}let W=null;return Object.freeze({url:ce,controlToken:K,status:V,dispose(){return W||(W=(async()=>{C=!0,L&&clearInterval(L),k&&clearTimeout(k),L=null,k=null;let t=E;if(t){H.add(t);let r=de(t,"exit").catch(()=>[]);t.kill("SIGTERM"),await Promise.race([r,new Promise(o=>setTimeout(o,5e3))]),t.exitCode===null&&t.signalCode===null&&t.kill("SIGKILL")}await fe(N),await pe(F,j),await _e(Y,ae)})(),W)}})}function Ve(e){if(e===void 0)return Object.freeze({});if(!e||typeof e!="object"||Array.isArray(e))throw new Error("Editor Server Plugin snapshots must be an object.");return Object.freeze(structuredClone(e))}function We(e,n){let i=f.resolve(e,n.rootPath);if(Te(e,i,"built Editor root"),!n.release.trim()||!n.artifactId.trim())throw new Error("Editor build identity is incomplete.");return Object.freeze({...n,rootPath:i,build:Object.freeze({...n.build})})}function Be(e){if(!e||typeof e!="object"||Array.isArray(e)||e.schemaVersion!==1||typeof e.projectId!="string"||typeof e.projectName!="string"||!e.authoring||!Array.isArray(e.plugins))throw new Error("Editor Server Project definition is invalid.");return structuredClone(e)}async function ue(e,n,i,a,s,_=!1){if(!a||a.includes("\\")||a.split("/").includes("..")){u(n,404,{ok:!1,error:"file_not_found"});return}let T=f.resolve(i,a),w=f.relative(i,T);if(!w||w===".."||w.startsWith(`..${f.sep}`)||f.isAbsolute(w)){u(n,404,{ok:!1,error:"file_not_found"});return}let S=await re(T).catch(()=>null);if(!S?.isFile()){u(n,404,{ok:!1,error:"file_not_found"});return}let m=e.headers["accept-encoding"]??"",d=await Je(T,m);n.statusCode=200,n.setHeader("Cache-Control",s||_?"public, max-age=31536000, immutable":"no-cache"),n.setHeader("Content-Type",et(T)),n.setHeader("Content-Length",String(d?.stat.size??S.size)),d&&(n.setHeader("Content-Encoding",d.encoding),n.setHeader("Vary","Accept-Encoding")),e.method==="HEAD"?n.end():je(d?.path??T).pipe(n)}async function Je(e,n){let i=n.toLowerCase().split(",").map(s=>s.trim()),a=s=>i.some(_=>{let[T,...w]=_.split(";").map(S=>S.trim());return T!==s&&T!=="*"?!1:!w.some(S=>S==="q=0"||S==="q=0.0")});for(let s of a("br")?[{suffix:".br",encoding:"br"},{suffix:".gz",encoding:"gzip"}]:[{suffix:".gz",encoding:"gzip"}]){if(s.encoding==="gzip"&&!a("gzip"))continue;let _=`${e}${s.suffix}`,T=await re(_).catch(()=>null);if(T?.isFile())return{path:_,encoding:s.encoding,stat:T}}return null}async function ze(e){let n=(await M(e,"utf8")).trim();if(!n)throw new Error(`Editor Server secret is empty: ${e}`);return n}async function Ke(e,n){await Ee(f.dirname(e),{recursive:!0,mode:448});let i={pid:process.pid,serverInstanceId:n,createdAt:new Date().toISOString()};try{let a=await le(e,"wx",384);return await a.writeFile(`${JSON.stringify(i)}
2
+ `,"utf8"),await a.close(),i}catch{let s=await M(e,"utf8").then(JSON.parse).catch(()=>null);if(typeof s?.pid=="number"&&Ye(s.pid))throw new Error(`Editor Server project lock is already owned by PID ${s.pid}.`);await B(e,{force:!0});let _=await le(e,"wx",384);return await _.writeFile(`${JSON.stringify(i)}
3
+ `,"utf8"),await _.close(),i}}async function _e(e,n){(await M(e,"utf8").then(JSON.parse).catch(()=>null))?.serverInstanceId===n.serverInstanceId&&await B(e,{force:!0})}function Ye(e){try{return process.kill(e,0),!0}catch{return!1}}async function Xe(e,n){await Ee(f.dirname(e),{recursive:!0,mode:448});let i=`${e}.${process.pid}.${me()}.tmp`;try{await Me(i,`${JSON.stringify(n,null,2)}
4
+ `,{encoding:"utf8",mode:384}),await Ge(i,e)}finally{await B(i,{force:!0}).catch(()=>{})}}async function pe(e,n){if(!n)return;let i=await M(e,"utf8").then(JSON.parse).catch(()=>null);i?.serviceInstanceId===n.serviceInstanceId&&i.pid===n.pid&&await B(e,{force:!0})}function Ze(e){if(!e||typeof e!="object")return!1;let n=e;return n.type==="heartbeat"?typeof n.serviceInstanceId=="string":n.type==="ready"&&typeof n.serviceInstanceId=="string"&&typeof n.internalUrl=="string"}async function Qe(e,n){let i=[],a=0;for await(let s of e){let _=Buffer.isBuffer(s)?s:Buffer.from(s);if(a+=_.byteLength,a>n)throw new Error("Editor Server request body is too large.");i.push(_)}return Buffer.concat(i)}function qe(e){e.setHeader("Access-Control-Allow-Origin","*"),e.setHeader("Access-Control-Expose-Headers","content-length, server-timing"),e.setHeader("Cross-Origin-Resource-Policy","cross-origin"),e.setHeader("Cross-Origin-Embedder-Policy","credentialless"),e.setHeader("X-Content-Type-Options","nosniff")}function u(e,n,i){e.statusCode=n,e.setHeader("Cache-Control","no-store"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.end(`${JSON.stringify(i)}
5
+ `)}function et(e){switch(f.extname(e).toLowerCase()){case".js":case".mjs":return"text/javascript; charset=utf-8";case".css":return"text/css; charset=utf-8";case".json":return"application/json; charset=utf-8";case".html":return"text/html; charset=utf-8";case".svg":return"image/svg+xml";case".png":return"image/png";case".jpg":case".jpeg":return"image/jpeg";case".webp":return"image/webp";case".glb":return"model/gltf-binary";case".gltf":return"model/gltf+json";default:return"application/octet-stream"}}function Te(e,n,i){let a=f.relative(e,n);if(!a||a===".."||a.startsWith(`..${f.sep}`)||f.isAbsolute(a))throw new Error(`Editor Server ${i} must be inside the canonical project root.`)}function tt(e){if(!Number.isSafeInteger(e)||e<1||e>65535)throw new Error("Editor Server port is invalid.");return e}function te(e,n){return Number.isSafeInteger(e)&&e>0?e:n}function rt(e,n){return`http://${e==="0.0.0.0"?"127.0.0.1":e}:${n}`}function U(e){return JSON.stringify(e).replace(/</g,"\\u003c").replace(/>/g,"\\u003e")}function nt(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;")}async function fe(e){e.listening&&await new Promise((n,i)=>e.close(a=>a?i(a):n()))}function G(e){return e instanceof Error?e.message:String(e)}it();var we=he.resolve(process.cwd()),g=JSON.parse(await ot(he.join(we,".fps-editor/current.json"),"utf8").catch(e=>{throw new Error("Editor deployment is not prepared. Run npm run prepare:server first.",{cause:e})}));if(g.schemaVersion!==3||typeof g.workerEntry!="string"||!g.projectDefinition||!Array.isArray(g.builds))throw new Error("Editor deployment state is invalid. Run npm run prepare:server again.");var ne=await Ie({projectRoot:we,workerEntry:g.workerEntry,projectDefinition:g.projectDefinition,pluginSnapshots:g.pluginSnapshots,builds:g.builds,activeRelease:process.env.FPS_EDITOR_RELEASE?.trim()||"a",host:process.env.FPS_EDITOR_SERVER_HOST?.trim()||"127.0.0.1",port:at(process.env.FPS_EDITOR_SERVER_PORT,3012),runtimeUrl:process.env.FPS_EDITOR_RUNTIME_URL?.trim()||"http://127.0.0.1:3011",runtimeBridgeTokenPath:".fps-editor/state/runtime-bridge-token",workerJournalPath:process.env.FPS_EDITOR_SERVER_JOURNAL_PATH,workerReadyTimeoutMs:6e4,workerHeartbeatTimeoutMs:1e4,workerRestartLimit:3,testControl:process.env.FPS_EDITOR_SERVER_TEST_CONTROL==="true",controlToken:process.env.FPS_EDITOR_SERVER_CONTROL_TOKEN});ne.controlToken&&console.log(`[fps-editor-server] test control token: ${ne.controlToken}`);var Re=!1;for(let e of["SIGINT","SIGTERM"])process.once(e,()=>{Re||(Re=!0,ne.dispose().finally(()=>{process.exitCode=e==="SIGINT"?130:143}))});function it(){let e=Number.parseInt(process.versions.node.split(".")[0]??"",10);if(!Number.isSafeInteger(e)||e<24||e>=27)throw new Error(`fps-editor-server requires Node.js >=24 <27; current version is ${process.versions.node}.`)}function at(e,n){if(!e?.trim())return n;let i=Number(e);if(!Number.isSafeInteger(i)||i<1||i>65535)throw new Error(`FPS_EDITOR_SERVER_PORT must be a valid port, got ${e}.`);return i}
@@ -1 +1 @@
1
- var l="/__fps_editor_agent/v1";var A=8*1024*1024;var v=256*1024;var h="/__fps_runtime/debug-authoring";function I(t){let n=t.token.trim();if(!n)throw new Error("runtime_debug_authoring_token_required");let e=b(t.path),r=new URL(l,f(t.editorUrl)),i=t.fetch??globalThis.fetch,a=new Set,s=[],u=()=>{a.clear(),s.length=0};return{name:"fps-editor-runtime-debug-authoring-bridge",apply:"serve",closeBundle:u,configureServer(c){c.httpServer?.once("close",u),c.middlewares.use(async(m,o,E)=>{if(S(m)!==e)return E();if(m.method!=="POST"){o.statusCode=405,o.setHeader("Allow","POST"),o.end();return}if(!R(m)){o.statusCode=403,o.end();return}try{let d=y(await T(m),a),p=await i(r,{method:"POST",headers:{authorization:`Bearer ${n}`,"content-type":"application/json"},body:JSON.stringify(d)}),g=await p.text();_(d,g,a,s),o.statusCode=p.status,o.setHeader("Content-Type","application/json; charset=utf-8"),o.end(g)}catch(d){t.logger?.warn?.(`[fps-editor] Runtime Debug authoring bridge failed: ${d instanceof Error?d.message:String(d)}`),o.statusCode=400,o.setHeader("Content-Type","application/json; charset=utf-8"),o.end(JSON.stringify({ok:!1,diagnostics:[{severity:"error",source:"transport",code:"runtimeDebug.bridgeRejected",message:d instanceof Error?d.message:String(d)}]}))}})}}}function y(t,n){if(!t||typeof t!="object"||Array.isArray(t))throw new Error("runtime_debug_authoring_payload_invalid");let e=t;if(e.kind==="authoring.snapshot")return{kind:"authoring.snapshot"};if(e.kind==="operation.get"&&"operationId"in e&&typeof e.operationId=="string"&&n.has(e.operationId))return{kind:"operation.get",operationId:e.operationId};if(e.kind==="command.invoke"&&(e.invocation.commandId==="document.companionState.get"||e.invocation.commandId==="runtimeDebug.commitAndSave"))return e;throw new Error("runtime_debug_authoring_request_not_allowed")}function _(t,n,e,r){let i;try{i=JSON.parse(n)}catch{return}if(!i||typeof i!="object"||Array.isArray(i))return;let a=i.operation;if(!a||typeof a!="object"||Array.isArray(a))return;let s=a.operationId,u=a.status;if(typeof s=="string"){if(u==="accepted"||u==="running"){if(t.kind==="command.invoke"&&!e.has(s))for(e.add(s),r.push(s);r.length>256;){let c=r.shift();c&&e.delete(c)}return}if(t.kind==="operation.get"){e.delete(s);let c=r.indexOf(s);c>=0&&r.splice(c,1)}}}function b(t){let n=t?.trim()||h;if(!n.startsWith("/")||n.startsWith("//"))throw new Error("runtime_debug_authoring_path_invalid");return n}function f(t){let n=new URL(t.trim());if(n.protocol!=="http:"&&n.protocol!=="https:")throw new Error("runtime_debug_authoring_editor_url_invalid");return n.toString()}function S(t){if(!t.url)return null;try{return new URL(t.url,"http://runtime.local").pathname}catch{return null}}function R(t){let n=t.headers.origin,e=t.headers.host;if(!n||!e)return!0;try{return new URL(n).host===e}catch{return!1}}async function T(t){let n=[],e=0;for await(let r of t){let i=Buffer.isBuffer(r)?r:Buffer.from(r);if(e+=i.byteLength,e>8*1024*1024)throw new Error("runtime_debug_authoring_body_too_large");n.push(i)}try{return JSON.parse(Buffer.concat(n).toString("utf8"))}catch{throw new Error("runtime_debug_authoring_json_invalid")}}export{I as createFpsGameEditorRuntimeDebugAuthoringBridgePlugin};
1
+ var l="/__fps_editor_agent/v1";var S=32*1024;var O=8*1024*1024;var v=256*1024;var h="/__fps_runtime/debug-authoring";function I(t){let n=t.token.trim();if(!n)throw new Error("runtime_debug_authoring_token_required");let e=_(t.path),r=new URL(l,f(t.editorUrl)),i=t.fetch??globalThis.fetch,a=new Set,s=[],u=()=>{a.clear(),s.length=0};return{name:"fps-editor-runtime-debug-authoring-bridge",apply:"serve",closeBundle:u,configureServer(c){c.httpServer?.once("close",u),c.middlewares.use(async(m,o,E)=>{if(A(m)!==e)return E();if(m.method!=="POST"){o.statusCode=405,o.setHeader("Allow","POST"),o.end();return}if(!R(m)){o.statusCode=403,o.end();return}try{let d=b(await T(m),a),p=await i(r,{method:"POST",headers:{authorization:`Bearer ${n}`,"content-type":"application/json"},body:JSON.stringify(d)}),g=await p.text();y(d,g,a,s),o.statusCode=p.status,o.setHeader("Content-Type","application/json; charset=utf-8"),o.end(g)}catch(d){t.logger?.warn?.(`[fps-editor] Runtime Debug authoring bridge failed: ${d instanceof Error?d.message:String(d)}`),o.statusCode=400,o.setHeader("Content-Type","application/json; charset=utf-8"),o.end(JSON.stringify({ok:!1,diagnostics:[{severity:"error",source:"transport",code:"runtimeDebug.bridgeRejected",message:d instanceof Error?d.message:String(d)}]}))}})}}}function b(t,n){if(!t||typeof t!="object"||Array.isArray(t))throw new Error("runtime_debug_authoring_payload_invalid");let e=t;if(e.kind==="authoring.snapshot")return{kind:"authoring.snapshot"};if(e.kind==="operation.get"&&"operationId"in e&&typeof e.operationId=="string"&&n.has(e.operationId))return{kind:"operation.get",operationId:e.operationId};if(e.kind==="command.invoke"&&(e.invocation.commandId==="document.companionState.get"||e.invocation.commandId==="runtimeDebug.commitAndSave"))return e;throw new Error("runtime_debug_authoring_request_not_allowed")}function y(t,n,e,r){let i;try{i=JSON.parse(n)}catch{return}if(!i||typeof i!="object"||Array.isArray(i))return;let a=i.operation;if(!a||typeof a!="object"||Array.isArray(a))return;let s=a.operationId,u=a.status;if(typeof s=="string"){if(u==="accepted"||u==="running"){if(t.kind==="command.invoke"&&!e.has(s))for(e.add(s),r.push(s);r.length>256;){let c=r.shift();c&&e.delete(c)}return}if(t.kind==="operation.get"){e.delete(s);let c=r.indexOf(s);c>=0&&r.splice(c,1)}}}function _(t){let n=t?.trim()||h;if(!n.startsWith("/")||n.startsWith("//"))throw new Error("runtime_debug_authoring_path_invalid");return n}function f(t){let n=new URL(t.trim());if(n.protocol!=="http:"&&n.protocol!=="https:")throw new Error("runtime_debug_authoring_editor_url_invalid");return n.toString()}function A(t){if(!t.url)return null;try{return new URL(t.url,"http://runtime.local").pathname}catch{return null}}function R(t){let n=t.headers.origin,e=t.headers.host;if(!n||!e)return!0;try{return new URL(n).host===e}catch{return!1}}async function T(t){let n=[],e=0;for await(let r of t){let i=Buffer.isBuffer(r)?r:Buffer.from(r);if(e+=i.byteLength,e>8*1024*1024)throw new Error("runtime_debug_authoring_body_too_large");n.push(i)}try{return JSON.parse(Buffer.concat(n).toString("utf8"))}catch{throw new Error("runtime_debug_authoring_json_invalid")}}export{I as createFpsGameEditorRuntimeDebugAuthoringBridgePlugin};