@fps-games/editor-deployment 0.2.3-beta.10 → 0.2.3-beta.11
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/licenses/THIRD_PARTY_NOTICES.txt +2 -2
- package/manifest.json +43 -43
- package/npm-shrinkwrap.json +2 -2
- package/package.json +6 -6
- package/public/assets/{index-Dhf9rSmv.js → index-DPWw7XQg.js} +1 -1
- package/public/assets/index-DPWw7XQg.js.br +0 -0
- package/public/assets/index-DPWw7XQg.js.gz +0 -0
- package/public/assets/{playable-sdk-zAYuBOLj.js → playable-sdk-x4OgoAVh.js} +1 -1
- package/public/assets/playable-sdk-x4OgoAVh.js.br +0 -0
- package/public/assets/playable-sdk-x4OgoAVh.js.gz +0 -0
- package/public/assets/{playable-standard-project-Brbyn9c4.js → playable-standard-project-ZEkeio_2.js} +65 -65
- package/public/assets/playable-standard-project-ZEkeio_2.js.br +0 -0
- package/public/assets/playable-standard-project-ZEkeio_2.js.gz +0 -0
- package/public/index.html +3 -3
- package/public/index.html.br +0 -0
- package/public/index.html.gz +0 -0
- package/runtime/editor-control-plane.mjs +5 -5
- package/worker/editor-service-worker.mjs +23 -23
- package/public/assets/index-Dhf9rSmv.js.br +0 -0
- package/public/assets/index-Dhf9rSmv.js.gz +0 -0
- package/public/assets/playable-sdk-zAYuBOLj.js.br +0 -0
- package/public/assets/playable-sdk-zAYuBOLj.js.gz +0 -0
- package/public/assets/playable-standard-project-Brbyn9c4.js.br +0 -0
- package/public/assets/playable-standard-project-Brbyn9c4.js.gz +0 -0
|
Binary file
|
|
Binary file
|
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-
|
|
14
|
-
<script type="module" crossorigin src="./assets/index-
|
|
13
|
+
<script type="importmap">{"imports":{"@fps-games/editor/playable-sdk":"./assets/playable-sdk-x4OgoAVh.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-DPWw7XQg.js"></script>
|
|
15
15
|
<link rel="modulepreload" crossorigin href="./assets/dynamicTexture-Cuby-VOT.js">
|
|
16
|
-
<link rel="modulepreload" crossorigin href="./assets/playable-standard-project-
|
|
16
|
+
<link rel="modulepreload" crossorigin href="./assets/playable-standard-project-ZEkeio_2.js">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<canvas id="renderCanvas"></canvas>
|
package/public/index.html.br
CHANGED
|
Binary file
|
package/public/index.html.gz
CHANGED
|
Binary file
|
|
@@ -1,5 +1,5 @@
|
|
|
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
|
|
3
|
-
`,"utf8"),await _.close(),i}}async function
|
|
4
|
-
`,{encoding:"utf8",mode:384}),await Ge(i,e)}finally{await B(i,{force:!0}).catch(()=>{})}}async function
|
|
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
|
|
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 je,randomBytes as ee,randomUUID as fe}from"node:crypto";import{once as ce}from"node:events";import{createReadStream as Le}from"node:fs";import{mkdir as me,open as de,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 Ee(e){let n=await Ue(e.projectRoot),i=e.projectEpoch?.trim()||je("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(Ie(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,$=fe(),oe=new Date().toISOString(),J=ee(32).toString("base64url"),z=ee(32).toString("base64url"),we=await ze(f.resolve(n,e.runtimeBridgeTokenPath??".fps-editor/state/runtime-bridge-token")),K=e.testControl?e.controlToken?.trim()||ee(32).toString("base64url"):null,Se=e.runtimeUrl?.trim()||"http://127.0.0.1:3011",F=f.join(n,".fps-editor/state/agent-runtime.json"),Y=`${F}.owner`,ie=await Ke(Y,$),Oe=te(e.workerReadyTimeoutMs,3e4),ae=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,j=null,k=null,L=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 pe(N).catch(()=>{}),await ue(Y,ie).catch(()=>{}),t}j=setInterval(()=>{C||p!=="ready"||!b||!E||Date.now()-Date.parse(b)<=ae||(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(ae/3)))),j.unref?.();let se=`http://${a}:${s}`;y.info?.(`[fps-editor-server] ready at ${se}`);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:we,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",L={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,L)}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 _e(F,L),L=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"),R=`/__fps_editor/build/${encodeURIComponent(l.artifactId)}/`,h=[`<base href="${nt(R)}">`,"<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(Se)};`,"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}${h}`):`${h}${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(h=>h.artifactId===l);if(!I){u(r,404,{ok:!1,error:"editor_build_not_found"});return}let R=c.slice(3).map(decodeURIComponent).join("/");await le(t,r,I.rootPath,R,!0)}async function ke(t,r,o){let c=decodeURIComponent(o.pathname.slice(1));await le(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 h of["accept","authorization","content-type","if-none-match","range"]){let v=t.headers[h];typeof v=="string"&&(I[h]=v)}let R=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=R.status;for(let h of["content-type","content-length","etag","content-range","accept-ranges","server-timing"]){let v=R.headers.get(h);v&&r.setHeader(h,v)}r.setHeader("Cache-Control","no-store"),r.end(t.method==="HEAD"?void 0:Buffer.from(await R.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 R=E;p="restarting",H.add(R);let h=ce(R,"exit");R.kill("SIGTERM"),await h}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:se,controlToken:K,status:V,dispose(){return W||(W=(async()=>{C=!0,j&&clearInterval(j),k&&clearTimeout(k),j=null,k=null;let t=E;if(t){H.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 pe(N),await _e(F,L),await ue(Y,ie)})(),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(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 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 le(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():Le(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 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 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 de(e,"wx",384);return await _.writeFile(`${JSON.stringify(i)}
|
|
3
|
+
`,"utf8"),await _.close(),i}}async function ue(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 me(f.dirname(e),{recursive:!0,mode:448});let i=`${e}.${process.pid}.${fe()}.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 _e(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 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 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,"&").replace(/"/g,""").replace(/</g,"<")}async function pe(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 Re=he.resolve(process.cwd()),g=JSON.parse(await ot(he.join(Re,".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 Ee({projectRoot:Re,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 Te=!1;for(let e of["SIGINT","SIGTERM"])process.once(e,()=>{Te||(Te=!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}
|