@agimon-ai/doompi-web 0.0.1-alpha.13 → 0.0.1-alpha.14

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 (117) hide show
  1. package/dist/pwa/sw.js +1 -1
  2. package/dist/src/adapters/bundlePublication.cjs +2 -2
  3. package/dist/src/adapters/bundlePublication.cjs.map +1 -1
  4. package/dist/src/adapters/bundlePublication.mjs +2 -2
  5. package/dist/src/adapters/bundlePublication.mjs.map +1 -1
  6. package/dist/src/adapters/doomInitialization.cjs +1 -1
  7. package/dist/src/adapters/doomInitialization.cjs.map +1 -1
  8. package/dist/src/adapters/doomInitialization.mjs +1 -1
  9. package/dist/src/adapters/httpServer.cjs +2 -2
  10. package/dist/src/adapters/httpServer.cjs.map +1 -1
  11. package/dist/src/adapters/httpServer.d.cts.map +1 -1
  12. package/dist/src/adapters/httpServer.d.mts.map +1 -1
  13. package/dist/src/adapters/httpServer.mjs +2 -2
  14. package/dist/src/adapters/httpServer.mjs.map +1 -1
  15. package/dist/src/adapters/sessionHub.cjs +1 -1
  16. package/dist/src/adapters/sessionHub.cjs.map +1 -1
  17. package/dist/src/adapters/sessionHub.mjs +1 -1
  18. package/dist/src/adapters/sessionHub.mjs.map +1 -1
  19. package/dist/src/adapters/sessionWebComposition.cjs +2 -0
  20. package/dist/src/adapters/sessionWebComposition.cjs.map +1 -0
  21. package/dist/src/adapters/sessionWebComposition.mjs +2 -0
  22. package/dist/src/adapters/sessionWebComposition.mjs.map +1 -0
  23. package/dist/src/adapters/syncGuard.cjs +1 -1
  24. package/dist/src/adapters/syncGuard.cjs.map +1 -1
  25. package/dist/src/adapters/syncGuard.mjs +1 -1
  26. package/dist/src/adapters/syncGuard.mjs.map +1 -1
  27. package/dist/src/adapters/webBundler.cjs +3 -1
  28. package/dist/src/adapters/webBundler.cjs.map +1 -1
  29. package/dist/src/adapters/webBundler.d.cts +4 -2
  30. package/dist/src/adapters/webBundler.d.cts.map +1 -1
  31. package/dist/src/adapters/webBundler.d.mts +4 -2
  32. package/dist/src/adapters/webBundler.d.mts.map +1 -1
  33. package/dist/src/adapters/webBundler.mjs +3 -1
  34. package/dist/src/adapters/webBundler.mjs.map +1 -1
  35. package/dist/src/adapters/webPluginGenerate.cjs +2 -2
  36. package/dist/src/adapters/webPluginGenerate.cjs.map +1 -1
  37. package/dist/src/adapters/webPluginGenerate.mjs +2 -2
  38. package/dist/src/adapters/webPluginGenerate.mjs.map +1 -1
  39. package/dist/src/adapters/webPluginVite.cjs +1 -1
  40. package/dist/src/adapters/webPluginVite.cjs.map +1 -1
  41. package/dist/src/adapters/webPluginVite.mjs +1 -1
  42. package/dist/src/adapters/webPluginVite.mjs.map +1 -1
  43. package/dist/src/types/hub.cjs +1 -1
  44. package/dist/src/types/hub.cjs.map +1 -1
  45. package/dist/src/types/hub.d.cts +23 -2
  46. package/dist/src/types/hub.d.cts.map +1 -1
  47. package/dist/src/types/hub.d.mts +23 -2
  48. package/dist/src/types/hub.d.mts.map +1 -1
  49. package/dist/src/types/hub.mjs +1 -1
  50. package/dist/src/types/hub.mjs.map +1 -1
  51. package/dist/web/assets/CodeEditorView-BN7uAd3p.js +3 -0
  52. package/dist/web/assets/CodeEditorView-BN7uAd3p.js.map +1 -0
  53. package/dist/web/assets/dist-0dAKR2Se.js +4 -0
  54. package/dist/web/assets/dist-0dAKR2Se.js.map +1 -0
  55. package/dist/web/assets/dist-0xJw4xv3.js +2 -0
  56. package/dist/web/assets/{dist-Dc0vEh5H.js.map → dist-0xJw4xv3.js.map} +1 -1
  57. package/dist/web/assets/dist-Bnntc1cw.js +2 -0
  58. package/dist/web/assets/{dist-CSIKLWGg.js.map → dist-Bnntc1cw.js.map} +1 -1
  59. package/dist/web/assets/dist-Br15ikyX.js +24 -0
  60. package/dist/web/assets/{dist-x765ui50.js.map → dist-Br15ikyX.js.map} +1 -1
  61. package/dist/web/assets/dist-CYkOb3JV.js +3 -0
  62. package/dist/web/assets/dist-CYkOb3JV.js.map +1 -0
  63. package/dist/web/assets/dist-CpWM9HPM.js +2 -0
  64. package/dist/web/assets/{dist-CMcoiIPx.js.map → dist-CpWM9HPM.js.map} +1 -1
  65. package/dist/web/assets/dist-D6JgiCOh.js +7 -0
  66. package/dist/web/assets/{dist-Dusr83oZ.js.map → dist-D6JgiCOh.js.map} +1 -1
  67. package/dist/web/assets/dist-DLyMOTCg.js +10 -0
  68. package/dist/web/assets/{dist-CfQNkp46.js.map → dist-DLyMOTCg.js.map} +1 -1
  69. package/dist/web/assets/dist-Dhwss0kX.js +2 -0
  70. package/dist/web/assets/{dist-1N2cDSZc.js.map → dist-Dhwss0kX.js.map} +1 -1
  71. package/dist/web/assets/dist-DyJg-Ztx.js +2 -0
  72. package/dist/web/assets/{dist-BvJltgg9.js.map → dist-DyJg-Ztx.js.map} +1 -1
  73. package/dist/web/assets/dist-gTLPm2JK.js +2 -0
  74. package/dist/web/assets/{dist-DlhyAlgc.js.map → dist-gTLPm2JK.js.map} +1 -1
  75. package/dist/web/assets/index-BlyW0dqe.js +132 -0
  76. package/dist/web/assets/index-BlyW0dqe.js.map +1 -0
  77. package/dist/web/index.html +1 -1
  78. package/package.json +12 -10
  79. package/src/adapters/bundlePublication.ts +86 -0
  80. package/src/adapters/httpServer.ts +123 -76
  81. package/src/adapters/sessionHub.ts +156 -65
  82. package/src/adapters/sessionWebComposition.ts +59 -0
  83. package/src/adapters/syncGuard.ts +16 -7
  84. package/src/adapters/webBundler.ts +48 -5
  85. package/src/adapters/webPluginGenerate.ts +20 -6
  86. package/src/adapters/webPluginVite.ts +45 -0
  87. package/src/pwa/bundleCache.ts +69 -0
  88. package/src/pwa/serviceWorker.ts +220 -5
  89. package/src/pwa/workerClient.ts +13 -0
  90. package/src/types/hub.ts +23 -1
  91. package/src/web/app/Providers.tsx +3 -2
  92. package/src/web/app/sessionRuntime.ts +6 -2
  93. package/src/web/features/session/ToolCard.tsx +2 -0
  94. package/src/web/lib/composition.ts +51 -10
  95. package/src/web/lib/pluginRegistry.ts +251 -145
  96. package/src/web/lib/pluginRuntime.ts +251 -0
  97. package/src/web/routes/CockpitPage.tsx +2 -0
  98. package/src/web/routes/SettingsPage.tsx +2 -0
  99. package/src/web/stores/useWebPluginRegistry.ts +7 -0
  100. package/dist/src/adapters/webComposition.cjs +0 -3
  101. package/dist/src/adapters/webComposition.cjs.map +0 -1
  102. package/dist/src/adapters/webComposition.mjs +0 -3
  103. package/dist/src/adapters/webComposition.mjs.map +0 -1
  104. package/dist/web/assets/CodeEditorView-Cv2_Pqe1.js +0 -15
  105. package/dist/web/assets/CodeEditorView-Cv2_Pqe1.js.map +0 -1
  106. package/dist/web/assets/dist-1N2cDSZc.js +0 -2
  107. package/dist/web/assets/dist-BvJltgg9.js +0 -2
  108. package/dist/web/assets/dist-CMcoiIPx.js +0 -2
  109. package/dist/web/assets/dist-CSIKLWGg.js +0 -2
  110. package/dist/web/assets/dist-CVA2128k.js +0 -1
  111. package/dist/web/assets/dist-CfQNkp46.js +0 -10
  112. package/dist/web/assets/dist-Dc0vEh5H.js +0 -2
  113. package/dist/web/assets/dist-DlhyAlgc.js +0 -2
  114. package/dist/web/assets/dist-Dusr83oZ.js +0 -7
  115. package/dist/web/assets/dist-x765ui50.js +0 -24
  116. package/dist/web/assets/index-CdO0WgVA.js +0 -101
  117. package/dist/web/assets/index-CdO0WgVA.js.map +0 -1
package/dist/pwa/sw.js CHANGED
@@ -1,2 +1,2 @@
1
1
  (function(){var e=`/bundle-manifest.json`,t=/^[0-9a-f]{64}$/u,n=/^[A-Za-z0-9_-]+$/u,r=/^[a-z0-9][a-z0-9!#$&^_.+-]*\/[a-z0-9][a-z0-9!#$&^_.+-]*$/u;function i(e,t){let n=Object.keys(e).sort();return n.length===t.length&&n.every((e,n)=>e===t[n])}function a(e,t){return typeof e==`number`&&Number.isSafeInteger(e)&&e>=t}function o(e){return typeof e!=`string`||e.length<2||!e.startsWith(`/`)||e.includes(`\\`)||e.includes(`\0`)||e.includes(`?`)||e.includes(`#`)||/[^\x21-\x7e]/u.test(e)||e.includes(`//`)||/%(?![0-9a-f]{2})/iu.test(e)||/%(?:2e|2f|5c)/iu.test(e)?!1:e.split(`/`).slice(1).every(e=>e!==``&&e!==`.`&&e!==`..`)}function s(e){let t=[...e.assets].sort((e,t)=>e.path<t.path?-1:+(e.path>t.path)).map(e=>`${e.path}\0${e.sha256}\0${String(e.byteLength)}\0${e.contentType}`);return[String(e.version),String(e.revision),String(e.builtAt),...t].join(`
2
- `)}function c(e){if(typeof e!=`object`||!e||!i(e,[`assets`,`builtAt`,`revision`,`version`]))return!1;let n=e;if(n.version!==2||!a(n.revision,1)||!a(n.builtAt,0)||!Array.isArray(n.assets)||n.assets.length===0)return!1;let s=new Set;for(let e of n.assets){if(typeof e!=`object`||!e||!i(e,[`byteLength`,`contentType`,`path`,`sha256`]))return!1;let n=e;if(!o(n.path)||s.has(n.path)||typeof n.sha256!=`string`||!t.test(n.sha256)||!a(n.byteLength,0)||typeof n.contentType!=`string`||!r.test(n.contentType))return!1;s.add(n.path)}return s.has(`/index.html`)}function l(e){if(typeof e!=`object`||!e||!i(e,[`manifest`,`publicKey`,`signature`]))return!1;let t=e;return c(t.manifest)&&typeof t.signature==`string`&&t.signature.length===86&&n.test(t.signature)&&typeof t.publicKey==`string`&&t.publicKey.length<=512&&n.test(t.publicKey)}function u(e,t){return e.assets.find(e=>e.path===t)}function d(e){let t=e.replaceAll(`-`,`+`).replaceAll(`_`,`/`),n=t.padEnd(Math.ceil(t.length/4)*4,`=`),r=atob(n),i=new Uint8Array(r.length);for(let e=0;e<r.length;e+=1)i[e]=r.charCodeAt(e);return i.buffer}function f(e){return[...new Uint8Array(e)].map(e=>e.toString(16).padStart(2,`0`)).join(``)}async function ee(e,t,n=0){if(!l(e))return{ok:!1,failure:{code:`invalid-envelope`}};let r=e;if(r.publicKey!==t)return{ok:!1,failure:{code:`untrusted-public-key`}};if(!Number.isSafeInteger(n)||n<0||r.manifest.revision<n)return{ok:!1,failure:{code:`stale-revision`,minimumRevision:n,actualRevision:r.manifest.revision}};let i;try{i=await crypto.subtle.importKey(`spki`,d(t),{name:`ECDSA`,namedCurve:`P-256`},!1,[`verify`])}catch{return{ok:!1,failure:{code:`invalid-public-key`}}}try{return await crypto.subtle.verify({name:`ECDSA`,hash:`SHA-256`},i,d(r.signature),new TextEncoder().encode(s(r.manifest)))?{ok:!0,manifest:r.manifest}:{ok:!1,failure:{code:`invalid-signature`}}}catch{return{ok:!1,failure:{code:`invalid-signature`}}}}async function p(e,t,n){let r=u(e,t);return r===void 0?{ok:!1,failure:{code:`asset-not-listed`,path:t}}:n.byteLength===r.byteLength?f(await crypto.subtle.digest(`SHA-256`,n))===r.sha256?{ok:!0}:{ok:!1,failure:{code:`digest-mismatch`}}:{ok:!1,failure:{code:`byte-length-mismatch`,expected:r.byteLength,actual:n.byteLength}}}var m=2**32,h=`doompi/sealed/c2s/v1`,g=`doompi/sealed/s2c/v1`;function _(e){return e===`c2s`?h:g}function v(e){if(typeof e!=`object`||!e)return!1;let t=e;return t.v===1&&typeof t.n==`string`&&typeof t.c==`string`}function y(e){switch(e){case`version`:return`The other end is speaking a different envelope version.`;case`malformed`:return`A sealed message was not shaped like one.`;case`replay`:return`A sealed message arrived out of order or twice.`;case`auth`:return`A sealed message failed its authentication tag, so it was altered in transit.`;case`exhausted`:return`This channel reached its message limit and must be re-established.`}}var b={name:`ECDH`,namedCurve:`P-256`},x=`AES-GCM`;function S(e){let t;try{t=atob(e.replaceAll(`-`,`+`).replaceAll(`_`,`/`))}catch{return}let n=new Uint8Array(new ArrayBuffer(t.length));for(let e=0;e<t.length;e+=1)n[e]=t.charCodeAt(e);return n}function C(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t).replaceAll(`+`,`-`).replaceAll(`/`,`_`).replaceAll(`=`,``)}function w(e){let t=new Uint8Array(new ArrayBuffer(e.length));return t.set(e),t.buffer}async function T(e,t){let n=await crypto.subtle.importKey(`raw`,e,`HKDF`,!1,[`deriveKey`]);return await crypto.subtle.deriveKey({name:`HKDF`,hash:`SHA-256`,salt:w(new TextEncoder().encode(`doompi/sealed/v1`)),info:w(new TextEncoder().encode(_(t)))},n,{name:x,length:256},!1,[`encrypt`,`decrypt`])}function E(e,t){let n=new Uint8Array(12);return n.set(e.subarray(0,4),0),new DataView(n.buffer).setBigUint64(4,t,!1),n}function D(e,t,n){let r=0n,i=-1n;return{async seal(t){if(r>=BigInt(m))return{ok:!1,failure:`exhausted`};let i=E(n,r),a=await crypto.subtle.encrypt({name:x,iv:w(i),tagLength:128},e,w(t));return r+=1n,{ok:!0,envelope:{v:1,n:C(i),c:C(new Uint8Array(a))}}},async open(e){let n=e;if(typeof n?.n!=`string`||typeof n.c!=`string`)return{ok:!1,failure:`malformed`};if(n.v!==1)return{ok:!1,failure:`version`};if(!v(e))return{ok:!1,failure:`malformed`};let r=S(e.n),a=S(e.c);if(r===void 0||a===void 0||r.length!==12||a.length<16)return{ok:!1,failure:`malformed`};let o=new DataView(w(r)).getBigUint64(4,!1);if(o<=i)return{ok:!1,failure:`replay`};let s;try{s=await crypto.subtle.decrypt({name:x,iv:w(r),tagLength:128},t,w(a))}catch{return{ok:!1,failure:`auth`}}return i=o,{ok:!0,plaintext:new Uint8Array(s)}}}}async function O(e){if(globalThis.crypto?.subtle!==void 0)try{let t=await crypto.subtle.generateKey(b,!1,[`deriveBits`]),n=S(e);if(n===void 0)return;let r=await crypto.subtle.importKey(`raw`,w(n),b,!1,[]),i=await crypto.subtle.deriveBits({name:`ECDH`,public:r},t.privateKey,256),a=C(new Uint8Array(await crypto.subtle.exportKey(`raw`,t.publicKey)));return{channel:D(await T(i,`c2s`),await T(i,`s2c`),new Uint8Array(4)),clientPublicKey:a}}catch{return}}function k(){let e=Promise.resolve();return{run(t){let n=e.then(t,t);return e=n.catch(()=>void 0),n}}}var A=new TextEncoder,j=new TextDecoder;function te(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t)}function ne(e){let t=atob(e);return Uint8Array.from(t,e=>e.charCodeAt(0))}async function re(e){if(e!=null){if(typeof e==`string`)return A.encode(e);if(e instanceof URLSearchParams)return A.encode(e.toString());if(e instanceof Blob)return new Uint8Array(await e.arrayBuffer());if(e instanceof ArrayBuffer)return new Uint8Array(e);if(ArrayBuffer.isView(e))return new Uint8Array(e.buffer,e.byteOffset,e.byteLength);throw Error(`The sealed HTTP transport does not support this request body type.`)}}function ie(e){if(!e.startsWith(`/`))throw Error(`A sealed HTTP request must use a root-relative URL.`);return e}function M(e){if(typeof e!=`object`||!e)return!1;let t=e;return t.v===1&&typeof t.status==`number`&&Array.isArray(t.headers)&&t.headers.every(e=>Array.isArray(e)&&e.length===2&&e.every(e=>typeof e==`string`))&&typeof t.body==`string`}function N(){let e,t,n=k(),r=k(),i=k(),a,o=e=>{t=y(e)},s=async t=>{if(e===void 0)return;let n=await e.seal(t);return n.ok?JSON.stringify(n.envelope):o(n.failure)},c=async t=>{if(e===void 0)return;let n;try{n=JSON.parse(t)}catch{return o(`malformed`)}let r=await e.open(n);return r.ok?r.plaintext:o(r.failure)};return{active:()=>e!==void 0,lastFailure:()=>t,async connect(n){let r=await O(n);if(r===void 0){t=`This browser could not establish a sealed channel.`;return}return e=r.channel,t=void 0,r.clientPublicKey},relayRequestsThrough(e){a=e},reset(){e=void 0,t=void 0},async sealText(r){return e===void 0?r:await n.run(async()=>{let e=await s(A.encode(r));if(e===void 0)throw Error(t??`The active sealed channel could not encrypt a message.`);return e})},async openText(t){return e===void 0?t:await r.run(async()=>{let e=await c(t);return e===void 0?void 0:j.decode(e)})},async sealBinary(r){return e===void 0?r:await n.run(async()=>{let e=await s(r);if(e===void 0)throw Error(t??`The active sealed channel could not encrypt a message.`);return A.encode(e)})},async openBinary(t){return e===void 0?t:await r.run(async()=>await c(j.decode(t)))},async fetch(n,r){let o=a;return e===void 0||o===void 0?await fetch(n,r):await i.run(async()=>{let e=await re(r?.body),i={v:1,method:(r?.method??`GET`).toUpperCase(),target:ie(n),headers:Array.from(new Headers(r?.headers).entries()),...e===void 0?{}:{body:te(e)}},a=await s(A.encode(JSON.stringify(i)));if(a===void 0)return new Response(JSON.stringify({error:t}),{status:502});let l=await fetch(o,{method:`POST`,credentials:`same-origin`,headers:{"content-type":`application/json`},body:a}),u=await c(await l.text());if(u===void 0)return new Response(JSON.stringify({error:t}),{status:502});let d;try{d=JSON.parse(j.decode(u))}catch{d=void 0}if(!M(d))return t=`The sealed HTTP response was malformed.`,new Response(JSON.stringify({error:t}),{status:502});let f=ne(d.body);return new Response(d.status===204||d.status===205||d.status===304?null:f,{status:d.status,headers:d.headers})})}}}N();var P=`doompi:bundle-updated`,F=`doompi-pwa`,I=`state`,L=`active-bundle`;function R(e){return new Promise((t,n)=>{e.addEventListener(`success`,()=>t(e.result),{once:!0}),e.addEventListener(`error`,()=>n(e.error??Error(`The PWA database request failed.`)),{once:!0})})}function z(e){return new Promise((t,n)=>{e.addEventListener(`complete`,()=>t(),{once:!0}),e.addEventListener(`abort`,()=>n(e.error??Error(`The PWA transaction aborted.`)),{once:!0}),e.addEventListener(`error`,()=>n(e.error??Error(`The PWA transaction failed.`)),{once:!0})})}async function B(){let e=indexedDB.open(F,1);return e.addEventListener(`upgradeneeded`,()=>{let t=e.result;t.objectStoreNames.contains(`state`)||t.createObjectStore(I)}),await R(e)}async function V(){let e=await B();try{let t=e.transaction(I,`readonly`),n=await R(t.objectStore(I).get(L));return await z(t),n}finally{e.close()}}async function H(e){let t=await B();try{let n=t.transaction(I,`readwrite`,{durability:`strict`});n.objectStore(I).put(e,L),await z(n)}finally{t.close()}}async function U(){let e=await B();try{let t=e.transaction(I,`readwrite`,{durability:`strict`});t.objectStore(I).delete(L),await z(t)}finally{e.close()}}var W=self,G=`/bundle-assets/`,K=`doompi-bundle-`,q=`doompi:activate-bundle`,J=`doompi:refresh-bundle`,Y=`doompi:reset-bundle-trust`;function X(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function ae(e){if(!(!X(e)||typeof e.type!=`string`)){if(e.type===J||e.type===Y)return{type:e.type};if(!(e.type!==q||typeof e.publicKey!=`string`||!Number.isSafeInteger(e.minimumRevision)||Number(e.minimumRevision)<1))return{type:q,publicKey:e.publicKey,minimumRevision:Number(e.minimumRevision)}}}function oe(e){return[...new Uint8Array(e)].map(e=>e.toString(16).padStart(2,`0`)).join(``)}async function se(e){let t=new TextEncoder().encode(s(e));return oe(await crypto.subtle.digest(`SHA-256`,t))}function ce(t){return t===`/pair`||t===`/sw.js`||t===`/manifest.webmanifest`||t.startsWith(`/pwa/`)||t.startsWith(`/api/`)||t===e||t.startsWith(G)}async function Z(e,t){let n=await caches.open(e.cacheName),r=new URL(t.url),i=t.mode===`navigate`?`/index.html`:r.pathname;return await n.match(i)??new Response(`That verified cockpit asset is unavailable.`,{status:404})}async function le(){let t=await fetch(e,{credentials:`include`,cache:`no-store`,redirect:`error`});if(!t.ok)throw Error(`The signed manifest request failed (${String(t.status)}).`);return await t.json()}async function Q(e,t){let n=await V();if(n!==void 0&&n.signerPublicKey!==e)return{ok:!1,code:`signer-mismatch`,message:`The host signing key does not match the pinned key.`};let r;try{r=await le()}catch(e){return{ok:!1,code:`manifest-fetch`,message:e instanceof Error?e.message:String(e)}}let i=Math.max(t,n?.revision??0),a=await ee(r,e,i);if(!a.ok)return{ok:!1,code:a.failure.code,message:`The signed bundle manifest was refused.`};let o=await se(a.manifest);if(n?.revision===a.manifest.revision){if(n.manifestDigest!==o)return{ok:!1,code:`revision-conflict`,message:`The host reused a revision for different bundle bytes.`};if(await caches.has(n.cacheName))return{ok:!0,revision:n.revision}}let s=`${K}${String(a.manifest.revision)}-${o.slice(0,16)}`;await caches.delete(s);let c=await caches.open(s);try{for(let e of a.manifest.assets){let t=`${G}${String(a.manifest.revision)}${e.path}`,n=await fetch(t,{credentials:`include`,cache:`no-store`,redirect:`error`});if(!n.ok||n.redirected||new URL(n.url).origin!==W.location.origin)throw Error(`The raw bundle asset ${e.path} was unavailable.`);let r=await n.arrayBuffer(),i=await p(a.manifest,e.path,r);if(!i.ok)throw Error(`The raw bundle asset ${e.path} failed ${i.failure.code}.`);await c.put(e.path,new Response(r,{status:200,headers:{"Cache-Control":`no-store`,"Content-Length":String(e.byteLength),"Content-Type":e.contentType,"X-Content-Type-Options":`nosniff`}}))}let t={signerPublicKey:e,manifestDigest:o,revision:a.manifest.revision,cacheName:s};await H(t);for(let e of await caches.keys())e.startsWith(K)&&e!==t.cacheName&&e!==n?.cacheName&&await caches.delete(e);return{ok:!0,revision:t.revision}}catch(e){return await caches.delete(s),{ok:!1,code:`asset-verification`,message:e instanceof Error?e.message:String(e)}}}async function ue(e){if(e.type===Y){for(let e of await caches.keys())e.startsWith(K)&&await caches.delete(e);return await U(),{ok:!0}}if(e.type===q)return await Q(e.publicKey,e.minimumRevision);let t=await V();return t===void 0?{ok:!1,code:`no-pin`,message:`No host signing key is pinned.`}:await Q(t.signerPublicKey,t.revision)}W.addEventListener(`install`,e=>{e.waitUntil(W.skipWaiting())}),W.addEventListener(`activate`,e=>{e.waitUntil(W.clients.claim())}),W.addEventListener(`message`,e=>{let t=ae(e.data),n=e.ports[0];if(n!==void 0){if(t===void 0){n.postMessage({ok:!1,code:`invalid-request`,message:`The worker request was malformed.`});return}e.waitUntil(ue(t).then(e=>n.postMessage(e)).catch(e=>n.postMessage({ok:!1,code:`worker-failure`,message:e instanceof Error?e.message:String(e)})))}});var $;async function de(e){let t=await W.clients.matchAll({type:`window`});for(let n of t)n.postMessage({type:P,revision:e})}function fe(e){return $??=(async()=>{try{let t=await Q(e.signerPublicKey,e.revision);t.ok&&t.revision!==void 0&&t.revision>e.revision&&await de(t.revision)}catch{}finally{$=void 0}})(),$}async function pe(){let e=await V();e!==void 0&&await fe(e)}W.addEventListener(`fetch`,e=>{let t=new URL(e.request.url);t.origin!==W.location.origin||ce(t.pathname)||(e.request.mode===`navigate`&&e.waitUntil(pe()),e.respondWith(V().then(async t=>t===void 0?await fetch(e.request):await Z(t,e.request))))}),W.addEventListener(`push`,e=>{let t=`A live session needs your attention.`;try{let n=e.data?.json();X(n)&&n.body===t&&(t=n.body)}catch{}e.waitUntil(W.registration.showNotification(`DoomPi`,{body:t,tag:`doompi-live`,icon:`/pwa/icon-192.png`,badge:`/pwa/icon-192.png`,data:{url:`/`}}))}),W.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(W.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(async e=>{let t=e.find(e=>new URL(e.url).origin===W.location.origin);if(t!==void 0){await t.focus();return}await W.clients.openWindow(`/`)}))})})();
2
+ `)}function c(e){if(typeof e!=`object`||!e||!i(e,[`assets`,`builtAt`,`revision`,`version`]))return!1;let n=e;if(n.version!==2||!a(n.revision,1)||!a(n.builtAt,0)||!Array.isArray(n.assets)||n.assets.length===0)return!1;let s=new Set;for(let e of n.assets){if(typeof e!=`object`||!e||!i(e,[`byteLength`,`contentType`,`path`,`sha256`]))return!1;let n=e;if(!o(n.path)||s.has(n.path)||typeof n.sha256!=`string`||!t.test(n.sha256)||!a(n.byteLength,0)||typeof n.contentType!=`string`||!r.test(n.contentType))return!1;s.add(n.path)}return s.has(`/index.html`)}function l(e){if(typeof e!=`object`||!e||!i(e,[`manifest`,`publicKey`,`signature`]))return!1;let t=e;return c(t.manifest)&&typeof t.signature==`string`&&t.signature.length===86&&n.test(t.signature)&&typeof t.publicKey==`string`&&t.publicKey.length<=512&&n.test(t.publicKey)}function u(e,t){return e.assets.find(e=>e.path===t)}function d(e){let t=e.replaceAll(`-`,`+`).replaceAll(`_`,`/`),n=t.padEnd(Math.ceil(t.length/4)*4,`=`),r=atob(n),i=new Uint8Array(r.length);for(let e=0;e<r.length;e+=1)i[e]=r.charCodeAt(e);return i.buffer}function f(e){return[...new Uint8Array(e)].map(e=>e.toString(16).padStart(2,`0`)).join(``)}async function p(e,t,n=0){if(!l(e))return{ok:!1,failure:{code:`invalid-envelope`}};let r=e;if(r.publicKey!==t)return{ok:!1,failure:{code:`untrusted-public-key`}};if(!Number.isSafeInteger(n)||n<0||r.manifest.revision<n)return{ok:!1,failure:{code:`stale-revision`,minimumRevision:n,actualRevision:r.manifest.revision}};let i;try{i=await crypto.subtle.importKey(`spki`,d(t),{name:`ECDSA`,namedCurve:`P-256`},!1,[`verify`])}catch{return{ok:!1,failure:{code:`invalid-public-key`}}}try{return await crypto.subtle.verify({name:`ECDSA`,hash:`SHA-256`},i,d(r.signature),new TextEncoder().encode(s(r.manifest)))?{ok:!0,manifest:r.manifest}:{ok:!1,failure:{code:`invalid-signature`}}}catch{return{ok:!1,failure:{code:`invalid-signature`}}}}async function m(e,t,n){let r=u(e,t);return r===void 0?{ok:!1,failure:{code:`asset-not-listed`,path:t}}:n.byteLength===r.byteLength?f(await crypto.subtle.digest(`SHA-256`,n))===r.sha256?{ok:!0}:{ok:!1,failure:{code:`digest-mismatch`}}:{ok:!1,failure:{code:`byte-length-mismatch`,expected:r.byteLength,actual:n.byteLength}}}var h=2**32,ee=`doompi/sealed/c2s/v1`,te=`doompi/sealed/s2c/v1`;function ne(e){return e===`c2s`?ee:te}function re(e){if(typeof e!=`object`||!e)return!1;let t=e;return t.v===1&&typeof t.n==`string`&&typeof t.c==`string`}function ie(e){switch(e){case`version`:return`The other end is speaking a different envelope version.`;case`malformed`:return`A sealed message was not shaped like one.`;case`replay`:return`A sealed message arrived out of order or twice.`;case`auth`:return`A sealed message failed its authentication tag, so it was altered in transit.`;case`exhausted`:return`This channel reached its message limit and must be re-established.`}}var g={name:`ECDH`,namedCurve:`P-256`},_=`AES-GCM`;function v(e){let t;try{t=atob(e.replaceAll(`-`,`+`).replaceAll(`_`,`/`))}catch{return}let n=new Uint8Array(new ArrayBuffer(t.length));for(let e=0;e<t.length;e+=1)n[e]=t.charCodeAt(e);return n}function y(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t).replaceAll(`+`,`-`).replaceAll(`/`,`_`).replaceAll(`=`,``)}function b(e){let t=new Uint8Array(new ArrayBuffer(e.length));return t.set(e),t.buffer}async function x(e,t){let n=await crypto.subtle.importKey(`raw`,e,`HKDF`,!1,[`deriveKey`]);return await crypto.subtle.deriveKey({name:`HKDF`,hash:`SHA-256`,salt:b(new TextEncoder().encode(`doompi/sealed/v1`)),info:b(new TextEncoder().encode(ne(t)))},n,{name:_,length:256},!1,[`encrypt`,`decrypt`])}function ae(e,t){let n=new Uint8Array(12);return n.set(e.subarray(0,4),0),new DataView(n.buffer).setBigUint64(4,t,!1),n}function oe(e,t,n){let r=0n,i=-1n;return{async seal(t){if(r>=BigInt(h))return{ok:!1,failure:`exhausted`};let i=ae(n,r),a=await crypto.subtle.encrypt({name:_,iv:b(i),tagLength:128},e,b(t));return r+=1n,{ok:!0,envelope:{v:1,n:y(i),c:y(new Uint8Array(a))}}},async open(e){let n=e;if(typeof n?.n!=`string`||typeof n.c!=`string`)return{ok:!1,failure:`malformed`};if(n.v!==1)return{ok:!1,failure:`version`};if(!re(e))return{ok:!1,failure:`malformed`};let r=v(e.n),a=v(e.c);if(r===void 0||a===void 0||r.length!==12||a.length<16)return{ok:!1,failure:`malformed`};let o=new DataView(b(r)).getBigUint64(4,!1);if(o<=i)return{ok:!1,failure:`replay`};let s;try{s=await crypto.subtle.decrypt({name:_,iv:b(r),tagLength:128},t,b(a))}catch{return{ok:!1,failure:`auth`}}return i=o,{ok:!0,plaintext:new Uint8Array(s)}}}}async function se(e){if(globalThis.crypto?.subtle!==void 0)try{let t=await crypto.subtle.generateKey(g,!1,[`deriveBits`]),n=v(e);if(n===void 0)return;let r=await crypto.subtle.importKey(`raw`,b(n),g,!1,[]),i=await crypto.subtle.deriveBits({name:`ECDH`,public:r},t.privateKey,256),a=y(new Uint8Array(await crypto.subtle.exportKey(`raw`,t.publicKey)));return{channel:oe(await x(i,`c2s`),await x(i,`s2c`),new Uint8Array(4)),clientPublicKey:a}}catch{return}}function S(){let e=Promise.resolve();return{run(t){let n=e.then(t,t);return e=n.catch(()=>void 0),n}}}var C=new TextEncoder,w=new TextDecoder;function ce(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t)}function le(e){let t=atob(e);return Uint8Array.from(t,e=>e.charCodeAt(0))}async function ue(e){if(e!=null){if(typeof e==`string`)return C.encode(e);if(e instanceof URLSearchParams)return C.encode(e.toString());if(e instanceof Blob)return new Uint8Array(await e.arrayBuffer());if(e instanceof ArrayBuffer)return new Uint8Array(e);if(ArrayBuffer.isView(e))return new Uint8Array(e.buffer,e.byteOffset,e.byteLength);throw Error(`The sealed HTTP transport does not support this request body type.`)}}function T(e){if(!e.startsWith(`/`))throw Error(`A sealed HTTP request must use a root-relative URL.`);return e}function E(e){if(typeof e!=`object`||!e)return!1;let t=e;return t.v===1&&typeof t.status==`number`&&Array.isArray(t.headers)&&t.headers.every(e=>Array.isArray(e)&&e.length===2&&e.every(e=>typeof e==`string`))&&typeof t.body==`string`}function D(){let e,t,n=S(),r=S(),i=S(),a,o=e=>{t=ie(e)},s=async t=>{if(e===void 0)return;let n=await e.seal(t);return n.ok?JSON.stringify(n.envelope):o(n.failure)},c=async t=>{if(e===void 0)return;let n;try{n=JSON.parse(t)}catch{return o(`malformed`)}let r=await e.open(n);return r.ok?r.plaintext:o(r.failure)};return{active:()=>e!==void 0,lastFailure:()=>t,async connect(n){let r=await se(n);if(r===void 0){t=`This browser could not establish a sealed channel.`;return}return e=r.channel,t=void 0,r.clientPublicKey},relayRequestsThrough(e){a=e},reset(){e=void 0,t=void 0},async sealText(r){return e===void 0?r:await n.run(async()=>{let e=await s(C.encode(r));if(e===void 0)throw Error(t??`The active sealed channel could not encrypt a message.`);return e})},async openText(t){return e===void 0?t:await r.run(async()=>{let e=await c(t);return e===void 0?void 0:w.decode(e)})},async sealBinary(r){return e===void 0?r:await n.run(async()=>{let e=await s(r);if(e===void 0)throw Error(t??`The active sealed channel could not encrypt a message.`);return C.encode(e)})},async openBinary(t){return e===void 0?t:await r.run(async()=>await c(w.decode(t)))},async fetch(n,r){let o=a;return e===void 0||o===void 0?await fetch(n,r):await i.run(async()=>{let e=await ue(r?.body),i={v:1,method:(r?.method??`GET`).toUpperCase(),target:T(n),headers:Array.from(new Headers(r?.headers).entries()),...e===void 0?{}:{body:ce(e)}},a=await s(C.encode(JSON.stringify(i)));if(a===void 0)return new Response(JSON.stringify({error:t}),{status:502});let l=await fetch(o,{method:`POST`,credentials:`same-origin`,headers:{"content-type":`application/json`},body:a}),u=await c(await l.text());if(u===void 0)return new Response(JSON.stringify({error:t}),{status:502});let d;try{d=JSON.parse(w.decode(u))}catch{d=void 0}if(!E(d))return t=`The sealed HTTP response was malformed.`,new Response(JSON.stringify({error:t}),{status:502});let f=le(d.body);return new Response(d.status===204||d.status===205||d.status===304?null:f,{status:d.status,headers:d.headers})})}}}D();var de=`doompi:bundle-updated`,fe=`doompi-pwa`,O=`state`,k=`active-bundle`,A=`plugin-composition:`;function j(e){return new Promise((t,n)=>{e.addEventListener(`success`,()=>t(e.result),{once:!0}),e.addEventListener(`error`,()=>n(e.error??Error(`The PWA database request failed.`)),{once:!0})})}function M(e){return new Promise((t,n)=>{e.addEventListener(`complete`,()=>t(),{once:!0}),e.addEventListener(`abort`,()=>n(e.error??Error(`The PWA transaction aborted.`)),{once:!0}),e.addEventListener(`error`,()=>n(e.error??Error(`The PWA transaction failed.`)),{once:!0})})}async function N(){let e=indexedDB.open(fe,1);return e.addEventListener(`upgradeneeded`,()=>{let t=e.result;t.objectStoreNames.contains(`state`)||t.createObjectStore(O)}),await j(e)}async function P(){let e=await N();try{let t=e.transaction(O,`readonly`),n=await j(t.objectStore(O).get(k));return await M(t),n}finally{e.close()}}async function pe(e){let t=await N();try{let n=t.transaction(O,`readwrite`,{durability:`strict`});n.objectStore(O).put(e,k),await M(n)}finally{t.close()}}async function me(){let e=await N();try{let t=e.transaction(O,`readwrite`,{durability:`strict`});t.objectStore(O).delete(k),await M(t)}finally{e.close()}}function F(e){return`${A}${e}`}async function I(e){let t=await N();try{let n=t.transaction(O,`readonly`),r=await j(n.objectStore(O).get(F(e)));return await M(n),r}finally{t.close()}}async function L(){let e=await N();try{let t=e.transaction(O,`readonly`),n=t.objectStore(O),r=await j(n.getAllKeys()),i=await Promise.all(r.filter(e=>typeof e==`string`&&e.startsWith(A)).map(async e=>await j(n.get(e))));return await M(t),i}finally{e.close()}}async function R(e){let t=await N();try{let n=t.transaction(O,`readwrite`,{durability:`strict`});n.objectStore(O).put(e,F(e.compositionId)),await M(n)}finally{t.close()}}async function z(e){let t=await N();try{let n=t.transaction(O,`readwrite`,{durability:`strict`});n.objectStore(O).delete(F(e)),await M(n)}finally{t.close()}}var B=self,V=`/bundle-assets/`,H=`doompi-bundle-`,U=`doompi-plugin-`,he=`/api/web-plugins/`,W=`/verified-plugins/`,ge=16,G=`doompi:activate-bundle`,K=`doompi:activate-plugin-composition`,_e=`doompi:refresh-bundle`,q=`doompi:reset-bundle-trust`;function J(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function ve(e){if(!(!J(e)||typeof e.type!=`string`)){if(e.type===_e||e.type===q)return{type:e.type};if(e.type===K){if(typeof e.compositionId!=`string`||!/^[a-f0-9]{64}$/u.test(e.compositionId)||!Number.isSafeInteger(e.revision)||Number(e.revision)<1||typeof e.manifestUrl!=`string`||typeof e.rawAssetBaseUrl!=`string`||typeof e.verifiedAssetBaseUrl!=`string`)return;let t=Number(e.revision),n=`${he}${e.compositionId}/${String(t)}`,r=`${W}${e.compositionId}/${String(t)}`;return e.manifestUrl!==`${n}/manifest`||e.rawAssetBaseUrl!==`${n}/assets`||e.verifiedAssetBaseUrl!==r?void 0:{type:K,compositionId:e.compositionId,revision:t,manifestUrl:e.manifestUrl,rawAssetBaseUrl:e.rawAssetBaseUrl,verifiedAssetBaseUrl:e.verifiedAssetBaseUrl}}if(!(e.type!==G||typeof e.publicKey!=`string`||!Number.isSafeInteger(e.minimumRevision)||Number(e.minimumRevision)<1))return{type:G,publicKey:e.publicKey,minimumRevision:Number(e.minimumRevision)}}}function ye(e){return[...new Uint8Array(e)].map(e=>e.toString(16).padStart(2,`0`)).join(``)}async function Y(e){let t=new TextEncoder().encode(s(e));return ye(await crypto.subtle.digest(`SHA-256`,t))}function be(t){return t===`/pair`||t===`/sw.js`||t===`/manifest.webmanifest`||t.startsWith(`/pwa/`)||t.startsWith(`/api/`)||t===e||t.startsWith(V)}async function xe(e,t){let n=await caches.open(e.cacheName),r=new URL(t.url),i=t.mode===`navigate`?`/index.html`:r.pathname;return await n.match(i)??new Response(`That verified cockpit asset is unavailable.`,{status:404})}async function Se(){let t=await fetch(e,{credentials:`include`,cache:`no-store`,redirect:`error`});if(!t.ok)throw Error(`The signed manifest request failed (${String(t.status)}).`);return await t.json()}async function Ce(e){let t=await fetch(e,{credentials:`include`,cache:`no-store`,redirect:`error`});if(!t.ok||t.redirected||new URL(t.url).origin!==B.location.origin)throw Error(`The signed plugin manifest request failed (${String(t.status)}).`);return await t.json()}async function we(){let e=(await L()).sort((e,t)=>t.lastUsedAt-e.lastUsedAt);for(let t of e.slice(ge))try{await z(t.compositionId),await caches.delete(t.cacheName)}catch{}}async function X(e){let t=await P();if(t===void 0)return{ok:!1,code:`no-pin`,message:`No trusted cockpit signing key is pinned.`};let n=await I(e.compositionId);if(n!==void 0&&n.signerPublicKey!==t.signerPublicKey)return{ok:!1,code:`signer-mismatch`,message:`The plugin signer does not match the cockpit signer.`};let r;try{r=await Ce(e.manifestUrl)}catch(e){return{ok:!1,code:`manifest-fetch`,message:e instanceof Error?e.message:String(e)}}let i=await p(r,t.signerPublicKey,e.revision);if(!i.ok||i.manifest.revision!==e.revision)return{ok:!1,code:`manifest-verification`,message:`The signed plugin manifest was refused.`};let a=await Y(i.manifest);if(n?.revision===e.revision){if(n.manifestDigest!==a)return{ok:!1,code:`revision-conflict`,message:`The plugin revision was reused for different bytes.`};if(await caches.has(n.cacheName))return await R({...n,lastUsedAt:Date.now()}),{ok:!0,revision:n.revision}}let o=`${U}${e.compositionId}-${String(e.revision)}-${a.slice(0,16)}`;await caches.delete(o);let s=await caches.open(o);try{for(let t of i.manifest.assets){let n=`${e.rawAssetBaseUrl}${t.path}`,r=await fetch(n,{credentials:`include`,cache:`no-store`,redirect:`error`});if(!r.ok||r.redirected||new URL(r.url).origin!==B.location.origin)throw Error(`The raw plugin asset ${t.path} was unavailable.`);let a=await r.arrayBuffer(),o=await m(i.manifest,t.path,a);if(!o.ok)throw Error(`The raw plugin asset ${t.path} failed ${o.failure.code}.`);await s.put(`${e.verifiedAssetBaseUrl}${t.path}`,new Response(a,{status:200,headers:{"Cache-Control":`no-store`,"Content-Length":String(t.byteLength),"Content-Type":t.contentType,"X-Content-Type-Options":`nosniff`}}))}let r={compositionId:e.compositionId,signerPublicKey:t.signerPublicKey,manifestDigest:a,revision:e.revision,cacheName:o,verifiedAssetBaseUrl:e.verifiedAssetBaseUrl,lastUsedAt:Date.now()};await R(r);try{n!==void 0&&n.cacheName!==r.cacheName&&await caches.delete(n.cacheName),await we()}catch{}return{ok:!0,revision:r.revision}}catch(e){return await caches.delete(o),{ok:!1,code:`asset-verification`,message:e instanceof Error?e.message:String(e)}}}var Z=new Map;function Te(e){let t=(Z.get(e.compositionId)??Promise.resolve({ok:!0})).then(async()=>await X(e),async()=>await X(e));return Z.set(e.compositionId,t),t.then(()=>{Z.get(e.compositionId)===t&&Z.delete(e.compositionId)},()=>{Z.get(e.compositionId)===t&&Z.delete(e.compositionId)}),t}async function Ee(e){let t=new URL(e.url),[n,r]=t.pathname.slice(18).split(`/`,2);if(n===void 0||r===void 0)return new Response(`That verified plugin asset is unavailable.`,{status:404});let i=await I(n);return i===void 0||String(i.revision)!==r||!t.pathname.startsWith(`${i.verifiedAssetBaseUrl}/`)?new Response(`That verified plugin asset is unavailable.`,{status:404}):await(await caches.open(i.cacheName)).match(t.pathname)??new Response(`That verified plugin asset is unavailable.`,{status:404})}async function Q(e,t){let n=await P();if(n!==void 0&&n.signerPublicKey!==e)return{ok:!1,code:`signer-mismatch`,message:`The host signing key does not match the pinned key.`};let r;try{r=await Se()}catch(e){return{ok:!1,code:`manifest-fetch`,message:e instanceof Error?e.message:String(e)}}let i=Math.max(t,n?.revision??0),a=await p(r,e,i);if(!a.ok)return{ok:!1,code:a.failure.code,message:`The signed bundle manifest was refused.`};let o=await Y(a.manifest);if(n?.revision===a.manifest.revision){if(n.manifestDigest!==o)return{ok:!1,code:`revision-conflict`,message:`The host reused a revision for different bundle bytes.`};if(await caches.has(n.cacheName))return{ok:!0,revision:n.revision}}let s=`${H}${String(a.manifest.revision)}-${o.slice(0,16)}`;await caches.delete(s);let c=await caches.open(s);try{for(let e of a.manifest.assets){let t=`${V}${String(a.manifest.revision)}${e.path}`,n=await fetch(t,{credentials:`include`,cache:`no-store`,redirect:`error`});if(!n.ok||n.redirected||new URL(n.url).origin!==B.location.origin)throw Error(`The raw bundle asset ${e.path} was unavailable.`);let r=await n.arrayBuffer(),i=await m(a.manifest,e.path,r);if(!i.ok)throw Error(`The raw bundle asset ${e.path} failed ${i.failure.code}.`);await c.put(e.path,new Response(r,{status:200,headers:{"Cache-Control":`no-store`,"Content-Length":String(e.byteLength),"Content-Type":e.contentType,"X-Content-Type-Options":`nosniff`}}))}let t={signerPublicKey:e,manifestDigest:o,revision:a.manifest.revision,cacheName:s};await pe(t);for(let e of await caches.keys())e.startsWith(H)&&e!==t.cacheName&&e!==n?.cacheName&&await caches.delete(e);return{ok:!0,revision:t.revision}}catch(e){return await caches.delete(s),{ok:!1,code:`asset-verification`,message:e instanceof Error?e.message:String(e)}}}async function De(e){if(e.type===q){for(let e of await caches.keys())(e.startsWith(H)||e.startsWith(U))&&await caches.delete(e);for(let e of await L())await z(e.compositionId);return await me(),{ok:!0}}if(e.type===G)return await Q(e.publicKey,e.minimumRevision);if(e.type===K)return await Te(e);let t=await P();return t===void 0?{ok:!1,code:`no-pin`,message:`No host signing key is pinned.`}:await Q(t.signerPublicKey,t.revision)}B.addEventListener(`install`,e=>{e.waitUntil(B.skipWaiting())}),B.addEventListener(`activate`,e=>{e.waitUntil(B.clients.claim())}),B.addEventListener(`message`,e=>{let t=ve(e.data),n=e.ports[0];if(n!==void 0){if(t===void 0){n.postMessage({ok:!1,code:`invalid-request`,message:`The worker request was malformed.`});return}e.waitUntil(De(t).then(e=>n.postMessage(e)).catch(e=>n.postMessage({ok:!1,code:`worker-failure`,message:e instanceof Error?e.message:String(e)})))}});var $;async function Oe(e){let t=await B.clients.matchAll({type:`window`});for(let n of t)n.postMessage({type:de,revision:e})}function ke(e){return $??=(async()=>{try{let t=await Q(e.signerPublicKey,e.revision);t.ok&&t.revision!==void 0&&t.revision>e.revision&&await Oe(t.revision)}catch{}finally{$=void 0}})(),$}async function Ae(){let e=await P();e!==void 0&&await ke(e)}B.addEventListener(`fetch`,e=>{let t=new URL(e.request.url);if(t.origin===B.location.origin){if(t.pathname.startsWith(W)){e.respondWith(Ee(e.request));return}be(t.pathname)||(e.request.mode===`navigate`&&e.waitUntil(Ae()),e.respondWith(P().then(async t=>t===void 0?await fetch(e.request):await xe(t,e.request))))}}),B.addEventListener(`push`,e=>{let t=`A live session needs your attention.`;try{let n=e.data?.json();J(n)&&n.body===t&&(t=n.body)}catch{}e.waitUntil(B.registration.showNotification(`DoomPi`,{body:t,tag:`doompi-live`,icon:`/pwa/icon-192.png`,badge:`/pwa/icon-192.png`,data:{url:`/`}}))}),B.addEventListener(`notificationclick`,e=>{e.notification.close(),e.waitUntil(B.clients.matchAll({type:`window`,includeUncontrolled:!0}).then(async e=>{let t=e.find(e=>new URL(e.url).origin===B.location.origin);if(t!==void 0){await t.focus();return}await B.clients.openWindow(`/`)}))})})();
@@ -1,3 +1,3 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:crypto"),r=require("@agimon-ai/doompi-web-security/node");const i=[];function a(e){let t=[...e.manifest.assets].sort((e,t)=>e.path.localeCompare(t.path)).map(e=>`${e.path}:${e.sha256}:${String(e.byteLength)}`).join(`
2
- `);return(0,n.createHash)(`sha256`).update(t).digest(`hex`)}function o(e,t){let n=e.signed.manifest.revision,r=a(e.signed),o=i.find(e=>e.digest===r&&e.revision<n);o!==void 0&&t(`the cockpit bundle repeats content already published as revision ${String(o.revision)}; paired devices will accept it as revision ${String(n)}`),i.push({revision:n,digest:r}),i.length>32&&i.shift()}function s(e){try{let n=t.default.statSync(e,{bigint:!0});return n.isDirectory()?`${String(n.dev)}:${String(n.ino)}:${String(n.mtimeNs)}`:void 0}catch{return}}function c(e){let t=e.onNotice??(()=>{}),n,i,a,c=t=>{let n=e.assetsDir(),r=t(n);return r===void 0?void 0:{signed:r,assetsDir:n}};try{n=(0,r.createBundleSigner)(e.stateDir,t);let l=c(e=>n?.sign(e));l===void 0?t(`the cockpit bundle is empty and cannot be published`):(i=l,a=s(l.assetsDir),o(i,t))}catch(e){t(`the cockpit bundle could not be signed: ${e instanceof Error?e.message:String(e)}`)}let l=()=>{if(n===void 0)return i;try{let e=c(e=>n?.refresh(e));if(e===void 0)return t(`the refreshed cockpit bundle is empty; keeping the last signed publication`),i;i=e,a=s(e.assetsDir),o(i,t)}catch(e){t(`the refreshed cockpit bundle was refused: ${e instanceof Error?e.message:String(e)}`)}return i},u=()=>{let t=i===void 0?void 0:s(e.assetsDir());if(i!==void 0&&e.assetsDir()!==i.assetsDir){l();return}t!==void 0&&t!==a&&l()};return{current:()=>(u(),i),trust:()=>(u(),i===void 0?void 0:{publicKey:i.signed.publicKey,revision:i.signed.manifest.revision}),refresh:l}}exports.createBundlePublication=c;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:crypto"),i=require("@agimon-ai/doompi-web-security/node");const a=[];function o(e){let t=[...e.manifest.assets].sort((e,t)=>e.path.localeCompare(t.path)).map(e=>`${e.path}:${e.sha256}:${String(e.byteLength)}`).join(`
2
+ `);return(0,r.createHash)(`sha256`).update(t).digest(`hex`)}function s(e,t){let n=e.signed.manifest.revision,r=o(e.signed),i=a.find(e=>e.digest===r&&e.revision<n);i!==void 0&&t(`the cockpit bundle repeats content already published as revision ${String(i.revision)}; paired devices will accept it as revision ${String(n)}`),a.push({revision:n,digest:r}),a.length>32&&a.shift()}function c(e){try{let n=t.default.statSync(e,{bigint:!0});return n.isDirectory()?`${String(n.dev)}:${String(n.ino)}:${String(n.mtimeNs)}`:void 0}catch{return}}function l(e){let t=e.onNotice??(()=>{}),n,r,a,o=t=>{let n=e.assetsDir(),r=t(n);return r===void 0?void 0:{signed:r,assetsDir:n}};try{n=(0,i.createBundleSigner)(e.stateDir,t);let l=o(e=>n?.sign(e));l===void 0?t(`the cockpit bundle is empty and cannot be published`):(r=l,a=c(l.assetsDir),s(r,t))}catch(e){t(`the cockpit bundle could not be signed: ${e instanceof Error?e.message:String(e)}`)}let l=()=>{if(n===void 0)return r;try{let e=o(e=>n?.refresh(e));if(e===void 0)return t(`the refreshed cockpit bundle is empty; keeping the last signed publication`),r;r=e,a=c(e.assetsDir),s(r,t)}catch(e){t(`the refreshed cockpit bundle was refused: ${e instanceof Error?e.message:String(e)}`)}return r},u=()=>{let t=r===void 0?void 0:c(e.assetsDir());if(r!==void 0&&e.assetsDir()!==r.assetsDir){l();return}t!==void 0&&t!==a&&l()};return{current:()=>(u(),r),trust:()=>(u(),r===void 0?void 0:{publicKey:r.signed.publicKey,revision:r.signed.manifest.revision}),refresh:l}}function u(e,r=()=>{}){let a;try{a=(0,i.createBundleSigner)(e,r)}catch(e){r(`plugin compositions could not be signed: ${e instanceof Error?e.message:String(e)}`)}let o=new Map,s=new Map,c=a===void 0?void 0:t.default.mkdtempSync(n.default.join(e,`plugin-publications-`)),l=!1,u=(e,t)=>`${e}:${String(t)}`;return{publish:(e,i)=>{if(l)return;let d=o.get(e);if(d!==void 0)return d;if(a===void 0||c===void 0)return;let f=t.default.mkdtempSync(n.default.join(c,`.staging-`)),p=n.default.join(f,`assets`);try{t.default.cpSync(i,p,{recursive:!0});let l=a.sign(p);if(l===void 0){r(`plugin composition '${e}' is empty and cannot be published`);return}let d=n.default.join(c,`${e}-${String(l.manifest.revision)}`);t.default.renameSync(p,d);let f={signed:l,assetsDir:d};for(o.set(e,f),s.set(u(e,l.manifest.revision),f);s.size>32;){let e=s.entries().next().value;if(e===void 0)break;s.delete(e[0]);for(let[t,n]of o)n===e[1]&&o.delete(t);t.default.rmSync(e[1].assetsDir,{recursive:!0,force:!0})}return f}catch(t){r(`plugin composition '${e}' could not be signed: ${t instanceof Error?t.message:String(t)}`);return}finally{t.default.rmSync(f,{recursive:!0,force:!0})}},get:(e,t)=>s.get(u(e,t)),publicKey:()=>a?.publicKey(),close:()=>{l||(l=!0,o.clear(),s.clear(),c!==void 0&&t.default.rmSync(c,{recursive:!0,force:!0}))}}}exports.createBundlePublication=l,exports.createPluginBundlePublication=u;
3
3
  //# sourceMappingURL=bundlePublication.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundlePublication.cjs","names":["createHash","fs","createBundleSigner"],"sources":["../../../src/adapters/bundlePublication.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport type { SignedBundleManifest } from '@agimon-ai/doompi-web-security';\nimport { createBundleSigner, type BundleSigner } from '@agimon-ai/doompi-web-security/node';\n\nexport interface BundleTrustView {\n publicKey: string;\n revision: number;\n}\n\n/**\n * One signed manifest and the directory it was signed over.\n *\n * They travel together because a page verifies each asset it fetches against\n * the manifest it was given. If the manifest advanced to a new sync generation\n * while asset reads still resolved against the previous directory, every fetch\n * would fail its digest check and the bundle would be refused. Handing callers\n * one record removes the chance of pairing them wrongly.\n */\nexport interface PublishedBundle {\n signed: SignedBundleManifest;\n assetsDir: string;\n}\n\nexport interface BundlePublication {\n current(): PublishedBundle | undefined;\n trust(): BundleTrustView | undefined;\n refresh(): PublishedBundle | undefined;\n}\n\nexport interface BundlePublicationOptions {\n /** Read on each publication, so a new sync generation can be adopted in place. */\n assetsDir: () => string;\n stateDir: string;\n onNotice?: (message: string) => void;\n}\n\n/**\n * Content this signer has published before, newest last.\n *\n * The signer revision only ever increases, which stops a paired device being\n * handed a manifest it has already seen superseded. It does not stop the same\n * hub re-serving older cockpit code under a fresh, higher revision, which is\n * what checking out an earlier commit and syncing does. That is allowed, but it\n * should not be silent, so republished content is named in the log.\n *\n * Process-local and bounded: this is a report, not a control.\n */\nconst publishedContent: Array<{ revision: number; digest: string }> = [];\nconst PUBLICATION_HISTORY_LIMIT = 32;\n\n/**\n * Digest of what the bundle contains, ignoring which publication carried it.\n *\n * `revision` and `builtAt` change on every signing, so a digest over the whole\n * manifest would never repeat and could never spot a downgrade.\n */\nfunction assetSetDigest(manifest: SignedBundleManifest): string {\n const assets = [...manifest.manifest.assets]\n .sort((left, right) => left.path.localeCompare(right.path))\n .map((asset) => `${asset.path}:${asset.sha256}:${String(asset.byteLength)}`)\n .join('\\n');\n return createHash('sha256').update(assets).digest('hex');\n}\n\nfunction noteRepublishedContent(bundle: PublishedBundle, notice: (message: string) => void): void {\n const revision = bundle.signed.manifest.revision;\n const digest = assetSetDigest(bundle.signed);\n const earlier = publishedContent.find((entry) => entry.digest === digest && entry.revision < revision);\n if (earlier !== undefined) {\n notice(\n `the cockpit bundle repeats content already published as revision ${String(earlier.revision)}; ` +\n `paired devices will accept it as revision ${String(revision)}`,\n );\n }\n publishedContent.push({ revision, digest });\n if (publishedContent.length > PUBLICATION_HISTORY_LIMIT) publishedContent.shift();\n}\n\nfunction assetRootIdentity(assetsDir: string): string | undefined {\n try {\n const stat = fs.statSync(assetsDir, { bigint: true });\n return stat.isDirectory() ? `${String(stat.dev)}:${String(stat.ino)}:${String(stat.mtimeNs)}` : undefined;\n } catch {\n return undefined;\n }\n}\n/** Holds the last complete signed publication while files at the synced path are replaced. */\nexport function createBundlePublication(options: BundlePublicationOptions): BundlePublication {\n const notice = options.onNotice ?? ((): void => {});\n let signer: BundleSigner | undefined;\n let published: PublishedBundle | undefined;\n let publishedRootIdentity: string | undefined;\n\n const signCurrent = (sign: (assetsDir: string) => SignedBundleManifest | undefined): PublishedBundle | undefined => {\n const assetsDir = options.assetsDir();\n const signed = sign(assetsDir);\n return signed === undefined ? undefined : { signed, assetsDir };\n };\n\n try {\n signer = createBundleSigner(options.stateDir, notice);\n const first = signCurrent((assetsDir) => signer?.sign(assetsDir));\n if (first === undefined) notice('the cockpit bundle is empty and cannot be published');\n else {\n published = first;\n publishedRootIdentity = assetRootIdentity(first.assetsDir);\n noteRepublishedContent(published, notice);\n }\n } catch (error) {\n notice(`the cockpit bundle could not be signed: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n const refresh = (): PublishedBundle | undefined => {\n if (signer === undefined) return published;\n try {\n const next = signCurrent((assetsDir) => signer?.refresh(assetsDir));\n if (next === undefined) {\n notice('the refreshed cockpit bundle is empty; keeping the last signed publication');\n return published;\n }\n published = next;\n publishedRootIdentity = assetRootIdentity(next.assetsDir);\n noteRepublishedContent(published, notice);\n } catch (error) {\n notice(`the refreshed cockpit bundle was refused: ${error instanceof Error ? error.message : String(error)}`);\n }\n return published;\n };\n\n const refreshAfterRootReplacement = (): void => {\n // A changed asset root means a new sync generation, or the same directory\n // replaced underneath; either way the last publication no longer describes\n // what would be served.\n const currentRootIdentity = published === undefined ? undefined : assetRootIdentity(options.assetsDir());\n if (published !== undefined && options.assetsDir() !== published.assetsDir) {\n refresh();\n return;\n }\n if (currentRootIdentity !== undefined && currentRootIdentity !== publishedRootIdentity) refresh();\n };\n\n return {\n current: () => {\n refreshAfterRootReplacement();\n return published;\n },\n trust: () => {\n refreshAfterRootReplacement();\n return published === undefined\n ? undefined\n : { publicKey: published.signed.publicKey, revision: published.signed.manifest.revision };\n },\n refresh,\n };\n}\n"],"mappings":"gLAgDA,MAAM,EAAgE,CAAC,EASvE,SAAS,EAAe,EAAwC,CAC9D,IAAM,EAAS,CAAC,GAAG,EAAS,SAAS,MAAM,CAAC,CACzC,MAAM,EAAM,IAAU,EAAK,KAAK,cAAc,EAAM,IAAI,CAAC,CAAC,CAC1D,IAAK,GAAU,GAAG,EAAM,KAAK,GAAG,EAAM,OAAO,GAAG,OAAO,EAAM,UAAU,GAAG,CAAC,CAC3E,KAAK;CAAI,EACZ,OAAA,EAAOA,EAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,CAAM,CAAC,CAAC,OAAO,KAAK,CACzD,CAEA,SAAS,EAAuB,EAAyB,EAAyC,CAChG,IAAM,EAAW,EAAO,OAAO,SAAS,SAClC,EAAS,EAAe,EAAO,MAAM,EACrC,EAAU,EAAiB,KAAM,GAAU,EAAM,SAAW,GAAU,EAAM,SAAW,CAAQ,EACjG,IAAY,IAAA,IACd,EACE,oEAAoE,OAAO,EAAQ,QAAQ,EAAE,8CAC9C,OAAO,CAAQ,GAChE,EAEF,EAAiB,KAAK,CAAE,WAAU,QAAO,CAAC,EACtC,EAAiB,OAAS,IAA2B,EAAiB,MAAM,CAClF,CAEA,SAAS,EAAkB,EAAuC,CAChE,GAAI,CACF,IAAM,EAAOC,EAAAA,QAAG,SAAS,EAAW,CAAE,OAAQ,EAAK,CAAC,EACpD,OAAO,EAAK,YAAY,EAAI,GAAG,OAAO,EAAK,GAAG,EAAE,GAAG,OAAO,EAAK,GAAG,EAAE,GAAG,OAAO,EAAK,OAAO,IAAM,IAAA,EAClG,MAAQ,CACN,MACF,CACF,CAEA,SAAgB,EAAwB,EAAsD,CAC5F,IAAM,EAAS,EAAQ,eAAyB,CAAC,GAC7C,EACA,EACA,EAEE,EAAe,GAA+F,CAClH,IAAM,EAAY,EAAQ,UAAU,EAC9B,EAAS,EAAK,CAAS,EAC7B,OAAO,IAAW,IAAA,GAAY,IAAA,GAAY,CAAE,SAAQ,WAAU,CAChE,EAEA,GAAI,CACF,GAAA,EAASC,EAAAA,mBAAAA,CAAmB,EAAQ,SAAU,CAAM,EACpD,IAAM,EAAQ,EAAa,GAAc,GAAQ,KAAK,CAAS,CAAC,EAC5D,IAAU,IAAA,GAAW,EAAO,qDAAqD,GAEnF,EAAY,EACZ,EAAwB,EAAkB,EAAM,SAAS,EACzD,EAAuB,EAAW,CAAM,EAE5C,OAAS,EAAO,CACd,EAAO,2CAA2C,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAAG,CAC5G,CAEA,IAAM,MAA6C,CACjD,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,GAAI,CACF,IAAM,EAAO,EAAa,GAAc,GAAQ,QAAQ,CAAS,CAAC,EAClE,GAAI,IAAS,IAAA,GAEX,OADA,EAAO,4EAA4E,EAC5E,EAET,EAAY,EACZ,EAAwB,EAAkB,EAAK,SAAS,EACxD,EAAuB,EAAW,CAAM,CAC1C,OAAS,EAAO,CACd,EAAO,6CAA6C,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAAG,CAC9G,CACA,OAAO,CACT,EAEM,MAA0C,CAI9C,IAAM,EAAsB,IAAc,IAAA,GAAY,IAAA,GAAY,EAAkB,EAAQ,UAAU,CAAC,EACvG,GAAI,IAAc,IAAA,IAAa,EAAQ,UAAU,IAAM,EAAU,UAAW,CAC1E,EAAQ,EACR,MACF,CACI,IAAwB,IAAA,IAAa,IAAwB,GAAuB,EAAQ,CAClG,EAEA,MAAO,CACL,aACE,EAA4B,EACrB,GAET,WACE,EAA4B,EACrB,IAAc,IAAA,GACjB,IAAA,GACA,CAAE,UAAW,EAAU,OAAO,UAAW,SAAU,EAAU,OAAO,SAAS,QAAS,GAE5F,SACF,CACF"}
1
+ {"version":3,"file":"bundlePublication.cjs","names":["createHash","fs","createBundleSigner","path"],"sources":["../../../src/adapters/bundlePublication.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { SignedBundleManifest } from '@agimon-ai/doompi-web-security';\nimport { createBundleSigner, type BundleSigner } from '@agimon-ai/doompi-web-security/node';\n\nexport interface BundleTrustView {\n publicKey: string;\n revision: number;\n}\n\n/**\n * One signed manifest and the directory it was signed over.\n *\n * They travel together because a page verifies each asset it fetches against\n * the manifest it was given. If the manifest advanced to a new sync generation\n * while asset reads still resolved against the previous directory, every fetch\n * would fail its digest check and the bundle would be refused. Handing callers\n * one record removes the chance of pairing them wrongly.\n */\nexport interface PublishedBundle {\n signed: SignedBundleManifest;\n assetsDir: string;\n}\n\nexport interface BundlePublication {\n current(): PublishedBundle | undefined;\n trust(): BundleTrustView | undefined;\n refresh(): PublishedBundle | undefined;\n}\n\n/** Signed immutable plugin compositions addressable by identity and revision. */\nexport interface PluginBundlePublication {\n publish(compositionId: string, assetsDir: string): PublishedBundle | undefined;\n get(compositionId: string, revision: number): PublishedBundle | undefined;\n publicKey(): string | undefined;\n close(): void;\n}\n\nexport interface BundlePublicationOptions {\n /** Read on each publication, so a new sync generation can be adopted in place. */\n assetsDir: () => string;\n stateDir: string;\n onNotice?: (message: string) => void;\n}\n\n/**\n * Content this signer has published before, newest last.\n *\n * The signer revision only ever increases, which stops a paired device being\n * handed a manifest it has already seen superseded. It does not stop the same\n * hub re-serving older cockpit code under a fresh, higher revision, which is\n * what checking out an earlier commit and syncing does. That is allowed, but it\n * should not be silent, so republished content is named in the log.\n *\n * Process-local and bounded: this is a report, not a control.\n */\nconst publishedContent: Array<{ revision: number; digest: string }> = [];\nconst PUBLICATION_HISTORY_LIMIT = 32;\n\n/**\n * Digest of what the bundle contains, ignoring which publication carried it.\n *\n * `revision` and `builtAt` change on every signing, so a digest over the whole\n * manifest would never repeat and could never spot a downgrade.\n */\nfunction assetSetDigest(manifest: SignedBundleManifest): string {\n const assets = [...manifest.manifest.assets]\n .sort((left, right) => left.path.localeCompare(right.path))\n .map((asset) => `${asset.path}:${asset.sha256}:${String(asset.byteLength)}`)\n .join('\\n');\n return createHash('sha256').update(assets).digest('hex');\n}\n\nfunction noteRepublishedContent(bundle: PublishedBundle, notice: (message: string) => void): void {\n const revision = bundle.signed.manifest.revision;\n const digest = assetSetDigest(bundle.signed);\n const earlier = publishedContent.find((entry) => entry.digest === digest && entry.revision < revision);\n if (earlier !== undefined) {\n notice(\n `the cockpit bundle repeats content already published as revision ${String(earlier.revision)}; ` +\n `paired devices will accept it as revision ${String(revision)}`,\n );\n }\n publishedContent.push({ revision, digest });\n if (publishedContent.length > PUBLICATION_HISTORY_LIMIT) publishedContent.shift();\n}\n\nfunction assetRootIdentity(assetsDir: string): string | undefined {\n try {\n const stat = fs.statSync(assetsDir, { bigint: true });\n return stat.isDirectory() ? `${String(stat.dev)}:${String(stat.ino)}:${String(stat.mtimeNs)}` : undefined;\n } catch {\n return undefined;\n }\n}\n/** Holds the last complete signed publication while files at the synced path are replaced. */\nexport function createBundlePublication(options: BundlePublicationOptions): BundlePublication {\n const notice = options.onNotice ?? ((): void => {});\n let signer: BundleSigner | undefined;\n let published: PublishedBundle | undefined;\n let publishedRootIdentity: string | undefined;\n\n const signCurrent = (sign: (assetsDir: string) => SignedBundleManifest | undefined): PublishedBundle | undefined => {\n const assetsDir = options.assetsDir();\n const signed = sign(assetsDir);\n return signed === undefined ? undefined : { signed, assetsDir };\n };\n\n try {\n signer = createBundleSigner(options.stateDir, notice);\n const first = signCurrent((assetsDir) => signer?.sign(assetsDir));\n if (first === undefined) notice('the cockpit bundle is empty and cannot be published');\n else {\n published = first;\n publishedRootIdentity = assetRootIdentity(first.assetsDir);\n noteRepublishedContent(published, notice);\n }\n } catch (error) {\n notice(`the cockpit bundle could not be signed: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n const refresh = (): PublishedBundle | undefined => {\n if (signer === undefined) return published;\n try {\n const next = signCurrent((assetsDir) => signer?.refresh(assetsDir));\n if (next === undefined) {\n notice('the refreshed cockpit bundle is empty; keeping the last signed publication');\n return published;\n }\n published = next;\n publishedRootIdentity = assetRootIdentity(next.assetsDir);\n noteRepublishedContent(published, notice);\n } catch (error) {\n notice(`the refreshed cockpit bundle was refused: ${error instanceof Error ? error.message : String(error)}`);\n }\n return published;\n };\n\n const refreshAfterRootReplacement = (): void => {\n // A changed asset root means a new sync generation, or the same directory\n // replaced underneath; either way the last publication no longer describes\n // what would be served.\n const currentRootIdentity = published === undefined ? undefined : assetRootIdentity(options.assetsDir());\n if (published !== undefined && options.assetsDir() !== published.assetsDir) {\n refresh();\n return;\n }\n if (currentRootIdentity !== undefined && currentRootIdentity !== publishedRootIdentity) refresh();\n };\n\n return {\n current: () => {\n refreshAfterRootReplacement();\n return published;\n },\n trust: () => {\n refreshAfterRootReplacement();\n return published === undefined\n ? undefined\n : { publicKey: published.signed.publicKey, revision: published.signed.manifest.revision };\n },\n refresh,\n };\n}\n\nconst PLUGIN_PUBLICATION_LIMIT = 32;\n\n/**\n * Signs synchronized plugin generations without replacing the stable shell publication.\n *\n * Publications are retained by the exact identity and revision advertised to pages, so\n * a concurrent sync cannot pair an old manifest with bytes from a new generation.\n */\nexport function createPluginBundlePublication(\n stateDir: string,\n onNotice: (message: string) => void = () => {},\n): PluginBundlePublication {\n let signer: BundleSigner | undefined;\n try {\n signer = createBundleSigner(stateDir, onNotice);\n } catch (error) {\n onNotice(`plugin compositions could not be signed: ${error instanceof Error ? error.message : String(error)}`);\n }\n const byComposition = new Map<string, PublishedBundle>();\n const byRoute = new Map<string, PublishedBundle>();\n const publicationRoot =\n signer === undefined ? undefined : fs.mkdtempSync(path.join(stateDir, 'plugin-publications-'));\n let closed = false;\n const routeKey = (compositionId: string, revision: number): string => `${compositionId}:${String(revision)}`;\n const publish = (compositionId: string, assetsDir: string): PublishedBundle | undefined => {\n if (closed) return undefined;\n const existing = byComposition.get(compositionId);\n if (existing !== undefined) return existing;\n if (signer === undefined || publicationRoot === undefined) return undefined;\n const stagingRoot = fs.mkdtempSync(path.join(publicationRoot, '.staging-'));\n const snapshot = path.join(stagingRoot, 'assets');\n try {\n fs.cpSync(assetsDir, snapshot, { recursive: true });\n const signed = signer.sign(snapshot);\n if (signed === undefined) {\n onNotice(`plugin composition '${compositionId}' is empty and cannot be published`);\n return undefined;\n }\n const retainedDirectory = path.join(publicationRoot, `${compositionId}-${String(signed.manifest.revision)}`);\n fs.renameSync(snapshot, retainedDirectory);\n const published = { signed, assetsDir: retainedDirectory };\n byComposition.set(compositionId, published);\n byRoute.set(routeKey(compositionId, signed.manifest.revision), published);\n while (byRoute.size > PLUGIN_PUBLICATION_LIMIT) {\n const oldest = byRoute.entries().next().value as [string, PublishedBundle] | undefined;\n if (oldest === undefined) break;\n byRoute.delete(oldest[0]);\n for (const [id, candidate] of byComposition) {\n if (candidate === oldest[1]) byComposition.delete(id);\n }\n fs.rmSync(oldest[1].assetsDir, { recursive: true, force: true });\n }\n return published;\n } catch (error) {\n onNotice(\n `plugin composition '${compositionId}' could not be signed: ${error instanceof Error ? error.message : String(error)}`,\n );\n return undefined;\n } finally {\n fs.rmSync(stagingRoot, { recursive: true, force: true });\n }\n };\n\n return {\n publish,\n get: (compositionId, revision) => byRoute.get(routeKey(compositionId, revision)),\n publicKey: () => signer?.publicKey(),\n close: () => {\n if (closed) return;\n closed = true;\n byComposition.clear();\n byRoute.clear();\n if (publicationRoot !== undefined) fs.rmSync(publicationRoot, { recursive: true, force: true });\n },\n };\n}\n"],"mappings":"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"}
@@ -1,3 +1,3 @@
1
- import e from"node:fs";import{createHash as t}from"node:crypto";import{createBundleSigner as n}from"@agimon-ai/doompi-web-security/node";const r=[];function i(e){let n=[...e.manifest.assets].sort((e,t)=>e.path.localeCompare(t.path)).map(e=>`${e.path}:${e.sha256}:${String(e.byteLength)}`).join(`
2
- `);return t(`sha256`).update(n).digest(`hex`)}function a(e,t){let n=e.signed.manifest.revision,a=i(e.signed),o=r.find(e=>e.digest===a&&e.revision<n);o!==void 0&&t(`the cockpit bundle repeats content already published as revision ${String(o.revision)}; paired devices will accept it as revision ${String(n)}`),r.push({revision:n,digest:a}),r.length>32&&r.shift()}function o(t){try{let n=e.statSync(t,{bigint:!0});return n.isDirectory()?`${String(n.dev)}:${String(n.ino)}:${String(n.mtimeNs)}`:void 0}catch{return}}function s(e){let t=e.onNotice??(()=>{}),r,i,s,c=t=>{let n=e.assetsDir(),r=t(n);return r===void 0?void 0:{signed:r,assetsDir:n}};try{r=n(e.stateDir,t);let l=c(e=>r?.sign(e));l===void 0?t(`the cockpit bundle is empty and cannot be published`):(i=l,s=o(l.assetsDir),a(i,t))}catch(e){t(`the cockpit bundle could not be signed: ${e instanceof Error?e.message:String(e)}`)}let l=()=>{if(r===void 0)return i;try{let e=c(e=>r?.refresh(e));if(e===void 0)return t(`the refreshed cockpit bundle is empty; keeping the last signed publication`),i;i=e,s=o(e.assetsDir),a(i,t)}catch(e){t(`the refreshed cockpit bundle was refused: ${e instanceof Error?e.message:String(e)}`)}return i},u=()=>{let t=i===void 0?void 0:o(e.assetsDir());if(i!==void 0&&e.assetsDir()!==i.assetsDir){l();return}t!==void 0&&t!==s&&l()};return{current:()=>(u(),i),trust:()=>(u(),i===void 0?void 0:{publicKey:i.signed.publicKey,revision:i.signed.manifest.revision}),refresh:l}}export{s as createBundlePublication};
1
+ import e from"node:fs";import t from"node:path";import{createHash as n}from"node:crypto";import{createBundleSigner as r}from"@agimon-ai/doompi-web-security/node";const i=[];function a(e){let t=[...e.manifest.assets].sort((e,t)=>e.path.localeCompare(t.path)).map(e=>`${e.path}:${e.sha256}:${String(e.byteLength)}`).join(`
2
+ `);return n(`sha256`).update(t).digest(`hex`)}function o(e,t){let n=e.signed.manifest.revision,r=a(e.signed),o=i.find(e=>e.digest===r&&e.revision<n);o!==void 0&&t(`the cockpit bundle repeats content already published as revision ${String(o.revision)}; paired devices will accept it as revision ${String(n)}`),i.push({revision:n,digest:r}),i.length>32&&i.shift()}function s(t){try{let n=e.statSync(t,{bigint:!0});return n.isDirectory()?`${String(n.dev)}:${String(n.ino)}:${String(n.mtimeNs)}`:void 0}catch{return}}function c(e){let t=e.onNotice??(()=>{}),n,i,a,c=t=>{let n=e.assetsDir(),r=t(n);return r===void 0?void 0:{signed:r,assetsDir:n}};try{n=r(e.stateDir,t);let l=c(e=>n?.sign(e));l===void 0?t(`the cockpit bundle is empty and cannot be published`):(i=l,a=s(l.assetsDir),o(i,t))}catch(e){t(`the cockpit bundle could not be signed: ${e instanceof Error?e.message:String(e)}`)}let l=()=>{if(n===void 0)return i;try{let e=c(e=>n?.refresh(e));if(e===void 0)return t(`the refreshed cockpit bundle is empty; keeping the last signed publication`),i;i=e,a=s(e.assetsDir),o(i,t)}catch(e){t(`the refreshed cockpit bundle was refused: ${e instanceof Error?e.message:String(e)}`)}return i},u=()=>{let t=i===void 0?void 0:s(e.assetsDir());if(i!==void 0&&e.assetsDir()!==i.assetsDir){l();return}t!==void 0&&t!==a&&l()};return{current:()=>(u(),i),trust:()=>(u(),i===void 0?void 0:{publicKey:i.signed.publicKey,revision:i.signed.manifest.revision}),refresh:l}}function l(n,i=()=>{}){let a;try{a=r(n,i)}catch(e){i(`plugin compositions could not be signed: ${e instanceof Error?e.message:String(e)}`)}let o=new Map,s=new Map,c=a===void 0?void 0:e.mkdtempSync(t.join(n,`plugin-publications-`)),l=!1,u=(e,t)=>`${e}:${String(t)}`;return{publish:(n,r)=>{if(l)return;let d=o.get(n);if(d!==void 0)return d;if(a===void 0||c===void 0)return;let f=e.mkdtempSync(t.join(c,`.staging-`)),p=t.join(f,`assets`);try{e.cpSync(r,p,{recursive:!0});let l=a.sign(p);if(l===void 0){i(`plugin composition '${n}' is empty and cannot be published`);return}let d=t.join(c,`${n}-${String(l.manifest.revision)}`);e.renameSync(p,d);let f={signed:l,assetsDir:d};for(o.set(n,f),s.set(u(n,l.manifest.revision),f);s.size>32;){let t=s.entries().next().value;if(t===void 0)break;s.delete(t[0]);for(let[e,n]of o)n===t[1]&&o.delete(e);e.rmSync(t[1].assetsDir,{recursive:!0,force:!0})}return f}catch(e){i(`plugin composition '${n}' could not be signed: ${e instanceof Error?e.message:String(e)}`);return}finally{e.rmSync(f,{recursive:!0,force:!0})}},get:(e,t)=>s.get(u(e,t)),publicKey:()=>a?.publicKey(),close:()=>{l||(l=!0,o.clear(),s.clear(),c!==void 0&&e.rmSync(c,{recursive:!0,force:!0}))}}}export{c as createBundlePublication,l as createPluginBundlePublication};
3
3
  //# sourceMappingURL=bundlePublication.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"bundlePublication.mjs","names":[],"sources":["../../../src/adapters/bundlePublication.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport type { SignedBundleManifest } from '@agimon-ai/doompi-web-security';\nimport { createBundleSigner, type BundleSigner } from '@agimon-ai/doompi-web-security/node';\n\nexport interface BundleTrustView {\n publicKey: string;\n revision: number;\n}\n\n/**\n * One signed manifest and the directory it was signed over.\n *\n * They travel together because a page verifies each asset it fetches against\n * the manifest it was given. If the manifest advanced to a new sync generation\n * while asset reads still resolved against the previous directory, every fetch\n * would fail its digest check and the bundle would be refused. Handing callers\n * one record removes the chance of pairing them wrongly.\n */\nexport interface PublishedBundle {\n signed: SignedBundleManifest;\n assetsDir: string;\n}\n\nexport interface BundlePublication {\n current(): PublishedBundle | undefined;\n trust(): BundleTrustView | undefined;\n refresh(): PublishedBundle | undefined;\n}\n\nexport interface BundlePublicationOptions {\n /** Read on each publication, so a new sync generation can be adopted in place. */\n assetsDir: () => string;\n stateDir: string;\n onNotice?: (message: string) => void;\n}\n\n/**\n * Content this signer has published before, newest last.\n *\n * The signer revision only ever increases, which stops a paired device being\n * handed a manifest it has already seen superseded. It does not stop the same\n * hub re-serving older cockpit code under a fresh, higher revision, which is\n * what checking out an earlier commit and syncing does. That is allowed, but it\n * should not be silent, so republished content is named in the log.\n *\n * Process-local and bounded: this is a report, not a control.\n */\nconst publishedContent: Array<{ revision: number; digest: string }> = [];\nconst PUBLICATION_HISTORY_LIMIT = 32;\n\n/**\n * Digest of what the bundle contains, ignoring which publication carried it.\n *\n * `revision` and `builtAt` change on every signing, so a digest over the whole\n * manifest would never repeat and could never spot a downgrade.\n */\nfunction assetSetDigest(manifest: SignedBundleManifest): string {\n const assets = [...manifest.manifest.assets]\n .sort((left, right) => left.path.localeCompare(right.path))\n .map((asset) => `${asset.path}:${asset.sha256}:${String(asset.byteLength)}`)\n .join('\\n');\n return createHash('sha256').update(assets).digest('hex');\n}\n\nfunction noteRepublishedContent(bundle: PublishedBundle, notice: (message: string) => void): void {\n const revision = bundle.signed.manifest.revision;\n const digest = assetSetDigest(bundle.signed);\n const earlier = publishedContent.find((entry) => entry.digest === digest && entry.revision < revision);\n if (earlier !== undefined) {\n notice(\n `the cockpit bundle repeats content already published as revision ${String(earlier.revision)}; ` +\n `paired devices will accept it as revision ${String(revision)}`,\n );\n }\n publishedContent.push({ revision, digest });\n if (publishedContent.length > PUBLICATION_HISTORY_LIMIT) publishedContent.shift();\n}\n\nfunction assetRootIdentity(assetsDir: string): string | undefined {\n try {\n const stat = fs.statSync(assetsDir, { bigint: true });\n return stat.isDirectory() ? `${String(stat.dev)}:${String(stat.ino)}:${String(stat.mtimeNs)}` : undefined;\n } catch {\n return undefined;\n }\n}\n/** Holds the last complete signed publication while files at the synced path are replaced. */\nexport function createBundlePublication(options: BundlePublicationOptions): BundlePublication {\n const notice = options.onNotice ?? ((): void => {});\n let signer: BundleSigner | undefined;\n let published: PublishedBundle | undefined;\n let publishedRootIdentity: string | undefined;\n\n const signCurrent = (sign: (assetsDir: string) => SignedBundleManifest | undefined): PublishedBundle | undefined => {\n const assetsDir = options.assetsDir();\n const signed = sign(assetsDir);\n return signed === undefined ? undefined : { signed, assetsDir };\n };\n\n try {\n signer = createBundleSigner(options.stateDir, notice);\n const first = signCurrent((assetsDir) => signer?.sign(assetsDir));\n if (first === undefined) notice('the cockpit bundle is empty and cannot be published');\n else {\n published = first;\n publishedRootIdentity = assetRootIdentity(first.assetsDir);\n noteRepublishedContent(published, notice);\n }\n } catch (error) {\n notice(`the cockpit bundle could not be signed: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n const refresh = (): PublishedBundle | undefined => {\n if (signer === undefined) return published;\n try {\n const next = signCurrent((assetsDir) => signer?.refresh(assetsDir));\n if (next === undefined) {\n notice('the refreshed cockpit bundle is empty; keeping the last signed publication');\n return published;\n }\n published = next;\n publishedRootIdentity = assetRootIdentity(next.assetsDir);\n noteRepublishedContent(published, notice);\n } catch (error) {\n notice(`the refreshed cockpit bundle was refused: ${error instanceof Error ? error.message : String(error)}`);\n }\n return published;\n };\n\n const refreshAfterRootReplacement = (): void => {\n // A changed asset root means a new sync generation, or the same directory\n // replaced underneath; either way the last publication no longer describes\n // what would be served.\n const currentRootIdentity = published === undefined ? undefined : assetRootIdentity(options.assetsDir());\n if (published !== undefined && options.assetsDir() !== published.assetsDir) {\n refresh();\n return;\n }\n if (currentRootIdentity !== undefined && currentRootIdentity !== publishedRootIdentity) refresh();\n };\n\n return {\n current: () => {\n refreshAfterRootReplacement();\n return published;\n },\n trust: () => {\n refreshAfterRootReplacement();\n return published === undefined\n ? undefined\n : { publicKey: published.signed.publicKey, revision: published.signed.manifest.revision };\n },\n refresh,\n };\n}\n"],"mappings":"yIAgDA,MAAM,EAAgE,CAAC,EASvE,SAAS,EAAe,EAAwC,CAC9D,IAAM,EAAS,CAAC,GAAG,EAAS,SAAS,MAAM,CAAC,CACzC,MAAM,EAAM,IAAU,EAAK,KAAK,cAAc,EAAM,IAAI,CAAC,CAAC,CAC1D,IAAK,GAAU,GAAG,EAAM,KAAK,GAAG,EAAM,OAAO,GAAG,OAAO,EAAM,UAAU,GAAG,CAAC,CAC3E,KAAK;CAAI,EACZ,OAAO,EAAW,QAAQ,CAAC,CAAC,OAAO,CAAM,CAAC,CAAC,OAAO,KAAK,CACzD,CAEA,SAAS,EAAuB,EAAyB,EAAyC,CAChG,IAAM,EAAW,EAAO,OAAO,SAAS,SAClC,EAAS,EAAe,EAAO,MAAM,EACrC,EAAU,EAAiB,KAAM,GAAU,EAAM,SAAW,GAAU,EAAM,SAAW,CAAQ,EACjG,IAAY,IAAA,IACd,EACE,oEAAoE,OAAO,EAAQ,QAAQ,EAAE,8CAC9C,OAAO,CAAQ,GAChE,EAEF,EAAiB,KAAK,CAAE,WAAU,QAAO,CAAC,EACtC,EAAiB,OAAS,IAA2B,EAAiB,MAAM,CAClF,CAEA,SAAS,EAAkB,EAAuC,CAChE,GAAI,CACF,IAAM,EAAO,EAAG,SAAS,EAAW,CAAE,OAAQ,EAAK,CAAC,EACpD,OAAO,EAAK,YAAY,EAAI,GAAG,OAAO,EAAK,GAAG,EAAE,GAAG,OAAO,EAAK,GAAG,EAAE,GAAG,OAAO,EAAK,OAAO,IAAM,IAAA,EAClG,MAAQ,CACN,MACF,CACF,CAEA,SAAgB,EAAwB,EAAsD,CAC5F,IAAM,EAAS,EAAQ,eAAyB,CAAC,GAC7C,EACA,EACA,EAEE,EAAe,GAA+F,CAClH,IAAM,EAAY,EAAQ,UAAU,EAC9B,EAAS,EAAK,CAAS,EAC7B,OAAO,IAAW,IAAA,GAAY,IAAA,GAAY,CAAE,SAAQ,WAAU,CAChE,EAEA,GAAI,CACF,EAAS,EAAmB,EAAQ,SAAU,CAAM,EACpD,IAAM,EAAQ,EAAa,GAAc,GAAQ,KAAK,CAAS,CAAC,EAC5D,IAAU,IAAA,GAAW,EAAO,qDAAqD,GAEnF,EAAY,EACZ,EAAwB,EAAkB,EAAM,SAAS,EACzD,EAAuB,EAAW,CAAM,EAE5C,OAAS,EAAO,CACd,EAAO,2CAA2C,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAAG,CAC5G,CAEA,IAAM,MAA6C,CACjD,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,GAAI,CACF,IAAM,EAAO,EAAa,GAAc,GAAQ,QAAQ,CAAS,CAAC,EAClE,GAAI,IAAS,IAAA,GAEX,OADA,EAAO,4EAA4E,EAC5E,EAET,EAAY,EACZ,EAAwB,EAAkB,EAAK,SAAS,EACxD,EAAuB,EAAW,CAAM,CAC1C,OAAS,EAAO,CACd,EAAO,6CAA6C,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAAG,CAC9G,CACA,OAAO,CACT,EAEM,MAA0C,CAI9C,IAAM,EAAsB,IAAc,IAAA,GAAY,IAAA,GAAY,EAAkB,EAAQ,UAAU,CAAC,EACvG,GAAI,IAAc,IAAA,IAAa,EAAQ,UAAU,IAAM,EAAU,UAAW,CAC1E,EAAQ,EACR,MACF,CACI,IAAwB,IAAA,IAAa,IAAwB,GAAuB,EAAQ,CAClG,EAEA,MAAO,CACL,aACE,EAA4B,EACrB,GAET,WACE,EAA4B,EACrB,IAAc,IAAA,GACjB,IAAA,GACA,CAAE,UAAW,EAAU,OAAO,UAAW,SAAU,EAAU,OAAO,SAAS,QAAS,GAE5F,SACF,CACF"}
1
+ {"version":3,"file":"bundlePublication.mjs","names":[],"sources":["../../../src/adapters/bundlePublication.ts"],"sourcesContent":["import { createHash } from 'node:crypto';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { SignedBundleManifest } from '@agimon-ai/doompi-web-security';\nimport { createBundleSigner, type BundleSigner } from '@agimon-ai/doompi-web-security/node';\n\nexport interface BundleTrustView {\n publicKey: string;\n revision: number;\n}\n\n/**\n * One signed manifest and the directory it was signed over.\n *\n * They travel together because a page verifies each asset it fetches against\n * the manifest it was given. If the manifest advanced to a new sync generation\n * while asset reads still resolved against the previous directory, every fetch\n * would fail its digest check and the bundle would be refused. Handing callers\n * one record removes the chance of pairing them wrongly.\n */\nexport interface PublishedBundle {\n signed: SignedBundleManifest;\n assetsDir: string;\n}\n\nexport interface BundlePublication {\n current(): PublishedBundle | undefined;\n trust(): BundleTrustView | undefined;\n refresh(): PublishedBundle | undefined;\n}\n\n/** Signed immutable plugin compositions addressable by identity and revision. */\nexport interface PluginBundlePublication {\n publish(compositionId: string, assetsDir: string): PublishedBundle | undefined;\n get(compositionId: string, revision: number): PublishedBundle | undefined;\n publicKey(): string | undefined;\n close(): void;\n}\n\nexport interface BundlePublicationOptions {\n /** Read on each publication, so a new sync generation can be adopted in place. */\n assetsDir: () => string;\n stateDir: string;\n onNotice?: (message: string) => void;\n}\n\n/**\n * Content this signer has published before, newest last.\n *\n * The signer revision only ever increases, which stops a paired device being\n * handed a manifest it has already seen superseded. It does not stop the same\n * hub re-serving older cockpit code under a fresh, higher revision, which is\n * what checking out an earlier commit and syncing does. That is allowed, but it\n * should not be silent, so republished content is named in the log.\n *\n * Process-local and bounded: this is a report, not a control.\n */\nconst publishedContent: Array<{ revision: number; digest: string }> = [];\nconst PUBLICATION_HISTORY_LIMIT = 32;\n\n/**\n * Digest of what the bundle contains, ignoring which publication carried it.\n *\n * `revision` and `builtAt` change on every signing, so a digest over the whole\n * manifest would never repeat and could never spot a downgrade.\n */\nfunction assetSetDigest(manifest: SignedBundleManifest): string {\n const assets = [...manifest.manifest.assets]\n .sort((left, right) => left.path.localeCompare(right.path))\n .map((asset) => `${asset.path}:${asset.sha256}:${String(asset.byteLength)}`)\n .join('\\n');\n return createHash('sha256').update(assets).digest('hex');\n}\n\nfunction noteRepublishedContent(bundle: PublishedBundle, notice: (message: string) => void): void {\n const revision = bundle.signed.manifest.revision;\n const digest = assetSetDigest(bundle.signed);\n const earlier = publishedContent.find((entry) => entry.digest === digest && entry.revision < revision);\n if (earlier !== undefined) {\n notice(\n `the cockpit bundle repeats content already published as revision ${String(earlier.revision)}; ` +\n `paired devices will accept it as revision ${String(revision)}`,\n );\n }\n publishedContent.push({ revision, digest });\n if (publishedContent.length > PUBLICATION_HISTORY_LIMIT) publishedContent.shift();\n}\n\nfunction assetRootIdentity(assetsDir: string): string | undefined {\n try {\n const stat = fs.statSync(assetsDir, { bigint: true });\n return stat.isDirectory() ? `${String(stat.dev)}:${String(stat.ino)}:${String(stat.mtimeNs)}` : undefined;\n } catch {\n return undefined;\n }\n}\n/** Holds the last complete signed publication while files at the synced path are replaced. */\nexport function createBundlePublication(options: BundlePublicationOptions): BundlePublication {\n const notice = options.onNotice ?? ((): void => {});\n let signer: BundleSigner | undefined;\n let published: PublishedBundle | undefined;\n let publishedRootIdentity: string | undefined;\n\n const signCurrent = (sign: (assetsDir: string) => SignedBundleManifest | undefined): PublishedBundle | undefined => {\n const assetsDir = options.assetsDir();\n const signed = sign(assetsDir);\n return signed === undefined ? undefined : { signed, assetsDir };\n };\n\n try {\n signer = createBundleSigner(options.stateDir, notice);\n const first = signCurrent((assetsDir) => signer?.sign(assetsDir));\n if (first === undefined) notice('the cockpit bundle is empty and cannot be published');\n else {\n published = first;\n publishedRootIdentity = assetRootIdentity(first.assetsDir);\n noteRepublishedContent(published, notice);\n }\n } catch (error) {\n notice(`the cockpit bundle could not be signed: ${error instanceof Error ? error.message : String(error)}`);\n }\n\n const refresh = (): PublishedBundle | undefined => {\n if (signer === undefined) return published;\n try {\n const next = signCurrent((assetsDir) => signer?.refresh(assetsDir));\n if (next === undefined) {\n notice('the refreshed cockpit bundle is empty; keeping the last signed publication');\n return published;\n }\n published = next;\n publishedRootIdentity = assetRootIdentity(next.assetsDir);\n noteRepublishedContent(published, notice);\n } catch (error) {\n notice(`the refreshed cockpit bundle was refused: ${error instanceof Error ? error.message : String(error)}`);\n }\n return published;\n };\n\n const refreshAfterRootReplacement = (): void => {\n // A changed asset root means a new sync generation, or the same directory\n // replaced underneath; either way the last publication no longer describes\n // what would be served.\n const currentRootIdentity = published === undefined ? undefined : assetRootIdentity(options.assetsDir());\n if (published !== undefined && options.assetsDir() !== published.assetsDir) {\n refresh();\n return;\n }\n if (currentRootIdentity !== undefined && currentRootIdentity !== publishedRootIdentity) refresh();\n };\n\n return {\n current: () => {\n refreshAfterRootReplacement();\n return published;\n },\n trust: () => {\n refreshAfterRootReplacement();\n return published === undefined\n ? undefined\n : { publicKey: published.signed.publicKey, revision: published.signed.manifest.revision };\n },\n refresh,\n };\n}\n\nconst PLUGIN_PUBLICATION_LIMIT = 32;\n\n/**\n * Signs synchronized plugin generations without replacing the stable shell publication.\n *\n * Publications are retained by the exact identity and revision advertised to pages, so\n * a concurrent sync cannot pair an old manifest with bytes from a new generation.\n */\nexport function createPluginBundlePublication(\n stateDir: string,\n onNotice: (message: string) => void = () => {},\n): PluginBundlePublication {\n let signer: BundleSigner | undefined;\n try {\n signer = createBundleSigner(stateDir, onNotice);\n } catch (error) {\n onNotice(`plugin compositions could not be signed: ${error instanceof Error ? error.message : String(error)}`);\n }\n const byComposition = new Map<string, PublishedBundle>();\n const byRoute = new Map<string, PublishedBundle>();\n const publicationRoot =\n signer === undefined ? undefined : fs.mkdtempSync(path.join(stateDir, 'plugin-publications-'));\n let closed = false;\n const routeKey = (compositionId: string, revision: number): string => `${compositionId}:${String(revision)}`;\n const publish = (compositionId: string, assetsDir: string): PublishedBundle | undefined => {\n if (closed) return undefined;\n const existing = byComposition.get(compositionId);\n if (existing !== undefined) return existing;\n if (signer === undefined || publicationRoot === undefined) return undefined;\n const stagingRoot = fs.mkdtempSync(path.join(publicationRoot, '.staging-'));\n const snapshot = path.join(stagingRoot, 'assets');\n try {\n fs.cpSync(assetsDir, snapshot, { recursive: true });\n const signed = signer.sign(snapshot);\n if (signed === undefined) {\n onNotice(`plugin composition '${compositionId}' is empty and cannot be published`);\n return undefined;\n }\n const retainedDirectory = path.join(publicationRoot, `${compositionId}-${String(signed.manifest.revision)}`);\n fs.renameSync(snapshot, retainedDirectory);\n const published = { signed, assetsDir: retainedDirectory };\n byComposition.set(compositionId, published);\n byRoute.set(routeKey(compositionId, signed.manifest.revision), published);\n while (byRoute.size > PLUGIN_PUBLICATION_LIMIT) {\n const oldest = byRoute.entries().next().value as [string, PublishedBundle] | undefined;\n if (oldest === undefined) break;\n byRoute.delete(oldest[0]);\n for (const [id, candidate] of byComposition) {\n if (candidate === oldest[1]) byComposition.delete(id);\n }\n fs.rmSync(oldest[1].assetsDir, { recursive: true, force: true });\n }\n return published;\n } catch (error) {\n onNotice(\n `plugin composition '${compositionId}' could not be signed: ${error instanceof Error ? error.message : String(error)}`,\n );\n return undefined;\n } finally {\n fs.rmSync(stagingRoot, { recursive: true, force: true });\n }\n };\n\n return {\n publish,\n get: (compositionId, revision) => byRoute.get(routeKey(compositionId, revision)),\n publicKey: () => signer?.publicKey(),\n close: () => {\n if (closed) return;\n closed = true;\n byComposition.clear();\n byRoute.clear();\n if (publicationRoot !== undefined) fs.rmSync(publicationRoot, { recursive: true, force: true });\n },\n };\n}\n"],"mappings":"kKAyDA,MAAM,EAAgE,CAAC,EASvE,SAAS,EAAe,EAAwC,CAC9D,IAAM,EAAS,CAAC,GAAG,EAAS,SAAS,MAAM,CAAC,CACzC,MAAM,EAAM,IAAU,EAAK,KAAK,cAAc,EAAM,IAAI,CAAC,CAAC,CAC1D,IAAK,GAAU,GAAG,EAAM,KAAK,GAAG,EAAM,OAAO,GAAG,OAAO,EAAM,UAAU,GAAG,CAAC,CAC3E,KAAK;CAAI,EACZ,OAAO,EAAW,QAAQ,CAAC,CAAC,OAAO,CAAM,CAAC,CAAC,OAAO,KAAK,CACzD,CAEA,SAAS,EAAuB,EAAyB,EAAyC,CAChG,IAAM,EAAW,EAAO,OAAO,SAAS,SAClC,EAAS,EAAe,EAAO,MAAM,EACrC,EAAU,EAAiB,KAAM,GAAU,EAAM,SAAW,GAAU,EAAM,SAAW,CAAQ,EACjG,IAAY,IAAA,IACd,EACE,oEAAoE,OAAO,EAAQ,QAAQ,EAAE,8CAC9C,OAAO,CAAQ,GAChE,EAEF,EAAiB,KAAK,CAAE,WAAU,QAAO,CAAC,EACtC,EAAiB,OAAS,IAA2B,EAAiB,MAAM,CAClF,CAEA,SAAS,EAAkB,EAAuC,CAChE,GAAI,CACF,IAAM,EAAO,EAAG,SAAS,EAAW,CAAE,OAAQ,EAAK,CAAC,EACpD,OAAO,EAAK,YAAY,EAAI,GAAG,OAAO,EAAK,GAAG,EAAE,GAAG,OAAO,EAAK,GAAG,EAAE,GAAG,OAAO,EAAK,OAAO,IAAM,IAAA,EAClG,MAAQ,CACN,MACF,CACF,CAEA,SAAgB,EAAwB,EAAsD,CAC5F,IAAM,EAAS,EAAQ,eAAyB,CAAC,GAC7C,EACA,EACA,EAEE,EAAe,GAA+F,CAClH,IAAM,EAAY,EAAQ,UAAU,EAC9B,EAAS,EAAK,CAAS,EAC7B,OAAO,IAAW,IAAA,GAAY,IAAA,GAAY,CAAE,SAAQ,WAAU,CAChE,EAEA,GAAI,CACF,EAAS,EAAmB,EAAQ,SAAU,CAAM,EACpD,IAAM,EAAQ,EAAa,GAAc,GAAQ,KAAK,CAAS,CAAC,EAC5D,IAAU,IAAA,GAAW,EAAO,qDAAqD,GAEnF,EAAY,EACZ,EAAwB,EAAkB,EAAM,SAAS,EACzD,EAAuB,EAAW,CAAM,EAE5C,OAAS,EAAO,CACd,EAAO,2CAA2C,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAAG,CAC5G,CAEA,IAAM,MAA6C,CACjD,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,GAAI,CACF,IAAM,EAAO,EAAa,GAAc,GAAQ,QAAQ,CAAS,CAAC,EAClE,GAAI,IAAS,IAAA,GAEX,OADA,EAAO,4EAA4E,EAC5E,EAET,EAAY,EACZ,EAAwB,EAAkB,EAAK,SAAS,EACxD,EAAuB,EAAW,CAAM,CAC1C,OAAS,EAAO,CACd,EAAO,6CAA6C,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAAG,CAC9G,CACA,OAAO,CACT,EAEM,MAA0C,CAI9C,IAAM,EAAsB,IAAc,IAAA,GAAY,IAAA,GAAY,EAAkB,EAAQ,UAAU,CAAC,EACvG,GAAI,IAAc,IAAA,IAAa,EAAQ,UAAU,IAAM,EAAU,UAAW,CAC1E,EAAQ,EACR,MACF,CACI,IAAwB,IAAA,IAAa,IAAwB,GAAuB,EAAQ,CAClG,EAEA,MAAO,CACL,aACE,EAA4B,EACrB,GAET,WACE,EAA4B,EACrB,IAAc,IAAA,GACjB,IAAA,GACA,CAAE,UAAW,EAAU,OAAO,UAAW,SAAU,EAAU,OAAO,SAAS,QAAS,GAE5F,SACF,CACF,CAUA,SAAgB,EACd,EACA,MAA4C,CAAC,EACpB,CACzB,IAAI,EACJ,GAAI,CACF,EAAS,EAAmB,EAAU,CAAQ,CAChD,OAAS,EAAO,CACd,EAAS,4CAA4C,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GAAG,CAC/G,CACA,IAAM,EAAgB,IAAI,IACpB,EAAU,IAAI,IACd,EACJ,IAAW,IAAA,GAAY,IAAA,GAAY,EAAG,YAAY,EAAK,KAAK,EAAU,sBAAsB,CAAC,EAC3F,EAAS,GACP,GAAY,EAAuB,IAA6B,GAAG,EAAc,GAAG,OAAO,CAAQ,IAwCzG,MAAO,CACL,SAxCe,EAAuB,IAAmD,CACzF,GAAI,EAAQ,OACZ,IAAM,EAAW,EAAc,IAAI,CAAa,EAChD,GAAI,IAAa,IAAA,GAAW,OAAO,EACnC,GAAI,IAAW,IAAA,IAAa,IAAoB,IAAA,GAAW,OAC3D,IAAM,EAAc,EAAG,YAAY,EAAK,KAAK,EAAiB,WAAW,CAAC,EACpE,EAAW,EAAK,KAAK,EAAa,QAAQ,EAChD,GAAI,CACF,EAAG,OAAO,EAAW,EAAU,CAAE,UAAW,EAAK,CAAC,EAClD,IAAM,EAAS,EAAO,KAAK,CAAQ,EACnC,GAAI,IAAW,IAAA,GAAW,CACxB,EAAS,uBAAuB,EAAc,mCAAmC,EACjF,MACF,CACA,IAAM,EAAoB,EAAK,KAAK,EAAiB,GAAG,EAAc,GAAG,OAAO,EAAO,SAAS,QAAQ,GAAG,EAC3G,EAAG,WAAW,EAAU,CAAiB,EACzC,IAAM,EAAY,CAAE,SAAQ,UAAW,CAAkB,EAGzD,IAFA,EAAc,IAAI,EAAe,CAAS,EAC1C,EAAQ,IAAI,EAAS,EAAe,EAAO,SAAS,QAAQ,EAAG,CAAS,EACjE,EAAQ,KAAO,IAA0B,CAC9C,IAAM,EAAS,EAAQ,QAAQ,CAAC,CAAC,KAAK,CAAC,CAAC,MACxC,GAAI,IAAW,IAAA,GAAW,MAC1B,EAAQ,OAAO,EAAO,EAAE,EACxB,IAAK,GAAM,CAAC,EAAI,KAAc,EACxB,IAAc,EAAO,IAAI,EAAc,OAAO,CAAE,EAEtD,EAAG,OAAO,EAAO,EAAE,CAAC,UAAW,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,CACjE,CACA,OAAO,CACT,OAAS,EAAO,CACd,EACE,uBAAuB,EAAc,yBAAyB,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,GACrH,EACA,MACF,QAAU,CACR,EAAG,OAAO,EAAa,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,CACzD,CACF,EAIE,KAAM,EAAe,IAAa,EAAQ,IAAI,EAAS,EAAe,CAAQ,CAAC,EAC/E,cAAiB,GAAQ,UAAU,EACnC,UAAa,CACP,IACJ,EAAS,GACT,EAAc,MAAM,EACpB,EAAQ,MAAM,EACV,IAAoB,IAAA,IAAW,EAAG,OAAO,EAAiB,CAAE,UAAW,GAAM,MAAO,EAAK,CAAC,EAChG,CACF,CACF"}
@@ -1,2 +1,2 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("@agimon-ai/doompi-config"),i=require("node:child_process"),a=require("node:module"),o=require("node:os");o=e.__toESM(o,1);const s=[`dist`,`bin`,`cli.mjs`];function c(){let e=n.default.dirname((0,a.createRequire)(require("url").pathToFileURL(__filename).href).resolve(`@agimon-ai/doompi/package.json`));return n.default.join(e,...s)}function l(){return new Promise((e,t)=>{let n=(0,i.spawn)(process.execPath,[c(),`init`],{cwd:o.default.homedir(),stdio:[`ignore`,`inherit`,`inherit`]});n.once(`error`,e=>t(Error(`doompi init could not start: ${e.message}`,{cause:e}))),n.once(`exit`,n=>{n===0?e():t(Error(`doompi init exited with code ${String(n)}`))})})}async function u(e={}){let n=e.homeDirectory??o.default.homedir(),i=(0,r.globalDoomConfigDirectory)(n);return!(e.exists??t.default.existsSync)(i)&&(e.onNotice?.(`${i} is missing; running doompi init`),await(e.runInit??l)(),!0)}exports.ensureDoomInitialized=u;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("node:path");n=e.__toESM(n,1);let r=require("node:module"),i=require("node:os");i=e.__toESM(i,1);let a=require("@agimon-ai/doompi-config"),o=require("node:child_process");const s=[`dist`,`bin`,`cli.mjs`];function c(){let e=n.default.dirname((0,r.createRequire)(require("url").pathToFileURL(__filename).href).resolve(`@agimon-ai/doompi/package.json`));return n.default.join(e,...s)}function l(){return new Promise((e,t)=>{let n=(0,o.spawn)(process.execPath,[c(),`init`],{cwd:i.default.homedir(),stdio:[`ignore`,`inherit`,`inherit`]});n.once(`error`,e=>t(Error(`doompi init could not start: ${e.message}`,{cause:e}))),n.once(`exit`,n=>{n===0?e():t(Error(`doompi init exited with code ${String(n)}`))})})}async function u(e={}){let n=e.homeDirectory??i.default.homedir(),r=(0,a.globalDoomConfigDirectory)(n);return!(e.exists??t.default.existsSync)(r)&&(e.onNotice?.(`${r} is missing; running doompi init`),await(e.runInit??l)(),!0)}exports.ensureDoomInitialized=u;
2
2
  //# sourceMappingURL=doomInitialization.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"doomInitialization.cjs","names":["path","createRequire","spawn","os","globalDoomConfigDirectory","fs"],"sources":["../../../src/adapters/doomInitialization.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config';\n\nconst DOOMPI_PACKAGE = '@agimon-ai/doompi';\nconst CLI_SEGMENTS = ['dist', 'bin', 'cli.mjs'];\n\nexport interface DoomInitializationOptions {\n homeDirectory?: string;\n onNotice?: (message: string) => void;\n /** Test seam over the global configuration directory check. */\n exists?: (directory: string) => boolean;\n /** Test seam over the child command. */\n runInit?: () => Promise<void>;\n}\n\nfunction bundledDoomPiCli(): string {\n const packageRoot = path.dirname(createRequire(import.meta.url).resolve(`${DOOMPI_PACKAGE}/package.json`));\n return path.join(packageRoot, ...CLI_SEGMENTS);\n}\n\nfunction runBundledInit(): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(process.execPath, [bundledDoomPiCli(), 'init'], {\n cwd: os.homedir(),\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n child.once('error', (error) =>\n reject(new Error(`doompi init could not start: ${error.message}`, { cause: error })),\n );\n child.once('exit', (code) => {\n if (code === 0) resolve();\n else reject(new Error(`doompi init exited with code ${String(code)}`));\n });\n });\n}\n\n/** Ensures the global configuration and managed Pi integration exist before the web hub starts. */\nexport async function ensureDoomInitialized(options: DoomInitializationOptions = {}): Promise<boolean> {\n const homeDirectory = options.homeDirectory ?? os.homedir();\n const configDirectory = globalDoomConfigDirectory(homeDirectory);\n if ((options.exists ?? fs.existsSync)(configDirectory)) return false;\n options.onNotice?.(`${configDirectory} is missing; running doompi init`);\n await (options.runInit ?? runBundledInit)();\n return true;\n}\n"],"mappings":"uRAOA,MACM,EAAe,CAAC,OAAQ,MAAO,SAAS,EAW9C,SAAS,GAA2B,CAClC,IAAM,EAAcA,EAAAA,QAAK,SAAA,EAAQC,EAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IAA6B,CAAC,CAAC,QAAQ,gCAAgC,CAAC,EACzG,OAAOD,EAAAA,QAAK,KAAK,EAAa,GAAG,CAAY,CAC/C,CAEA,SAAS,GAAgC,CACvC,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,IAAM,GAAA,EAAQE,EAAAA,MAAAA,CAAM,QAAQ,SAAU,CAAC,EAAiB,EAAG,MAAM,EAAG,CAClE,IAAKC,EAAAA,QAAG,QAAQ,EAChB,MAAO,CAAC,SAAU,UAAW,SAAS,CACxC,CAAC,EACD,EAAM,KAAK,QAAU,GACnB,EAAW,MAAM,gCAAgC,EAAM,UAAW,CAAE,MAAO,CAAM,CAAC,CAAC,CACrF,EACA,EAAM,KAAK,OAAS,GAAS,CACvB,IAAS,EAAG,EAAQ,EACnB,EAAW,MAAM,gCAAgC,OAAO,CAAI,GAAG,CAAC,CACvE,CAAC,CACH,CAAC,CACH,CAGA,eAAsB,EAAsB,EAAqC,CAAC,EAAqB,CACrG,IAAM,EAAgB,EAAQ,eAAiBA,EAAAA,QAAG,QAAQ,EACpD,GAAA,EAAkBC,EAAAA,0BAAAA,CAA0B,CAAa,EAI/D,MAHA,EAAK,EAAQ,QAAUC,EAAAA,QAAG,WAAA,CAAY,CAAe,IACrD,EAAQ,WAAW,GAAG,EAAgB,iCAAiC,EACvE,MAAO,EAAQ,SAAW,EAAA,CAAgB,EACnC,GACT"}
1
+ {"version":3,"file":"doomInitialization.cjs","names":["path","createRequire","spawn","os","globalDoomConfigDirectory","fs"],"sources":["../../../src/adapters/doomInitialization.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport fs from 'node:fs';\nimport { createRequire } from 'node:module';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { globalDoomConfigDirectory } from '@agimon-ai/doompi-config';\n\nconst DOOMPI_PACKAGE = '@agimon-ai/doompi';\nconst CLI_SEGMENTS = ['dist', 'bin', 'cli.mjs'];\n\nexport interface DoomInitializationOptions {\n homeDirectory?: string;\n onNotice?: (message: string) => void;\n /** Test seam over the global configuration directory check. */\n exists?: (directory: string) => boolean;\n /** Test seam over the child command. */\n runInit?: () => Promise<void>;\n}\n\nfunction bundledDoomPiCli(): string {\n const packageRoot = path.dirname(createRequire(import.meta.url).resolve(`${DOOMPI_PACKAGE}/package.json`));\n return path.join(packageRoot, ...CLI_SEGMENTS);\n}\n\nfunction runBundledInit(): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(process.execPath, [bundledDoomPiCli(), 'init'], {\n cwd: os.homedir(),\n stdio: ['ignore', 'inherit', 'inherit'],\n });\n child.once('error', (error) =>\n reject(new Error(`doompi init could not start: ${error.message}`, { cause: error })),\n );\n child.once('exit', (code) => {\n if (code === 0) resolve();\n else reject(new Error(`doompi init exited with code ${String(code)}`));\n });\n });\n}\n\n/** Ensures the global configuration and managed Pi integration exist before the web hub starts. */\nexport async function ensureDoomInitialized(options: DoomInitializationOptions = {}): Promise<boolean> {\n const homeDirectory = options.homeDirectory ?? os.homedir();\n const configDirectory = globalDoomConfigDirectory(homeDirectory);\n if ((options.exists ?? fs.existsSync)(configDirectory)) return false;\n options.onNotice?.(`${configDirectory} is missing; running doompi init`);\n await (options.runInit ?? runBundledInit)();\n return true;\n}\n"],"mappings":"2RAOA,MACM,EAAe,CAAC,OAAQ,MAAO,SAAS,EAW9C,SAAS,GAA2B,CAClC,IAAM,EAAcA,EAAAA,QAAK,SAAA,EAAQC,EAAAA,cAAAA,CAAAA,QAAAA,KAAAA,CAAAA,CAAAA,cAAAA,UAAAA,CAAAA,CAAAA,IAA6B,CAAC,CAAC,QAAQ,gCAAgC,CAAC,EACzG,OAAOD,EAAAA,QAAK,KAAK,EAAa,GAAG,CAAY,CAC/C,CAEA,SAAS,GAAgC,CACvC,OAAO,IAAI,SAAS,EAAS,IAAW,CACtC,IAAM,GAAA,EAAQE,EAAAA,MAAAA,CAAM,QAAQ,SAAU,CAAC,EAAiB,EAAG,MAAM,EAAG,CAClE,IAAKC,EAAAA,QAAG,QAAQ,EAChB,MAAO,CAAC,SAAU,UAAW,SAAS,CACxC,CAAC,EACD,EAAM,KAAK,QAAU,GACnB,EAAW,MAAM,gCAAgC,EAAM,UAAW,CAAE,MAAO,CAAM,CAAC,CAAC,CACrF,EACA,EAAM,KAAK,OAAS,GAAS,CACvB,IAAS,EAAG,EAAQ,EACnB,EAAW,MAAM,gCAAgC,OAAO,CAAI,GAAG,CAAC,CACvE,CAAC,CACH,CAAC,CACH,CAGA,eAAsB,EAAsB,EAAqC,CAAC,EAAqB,CACrG,IAAM,EAAgB,EAAQ,eAAiBA,EAAAA,QAAG,QAAQ,EACpD,GAAA,EAAkBC,EAAAA,0BAAAA,CAA0B,CAAa,EAI/D,MAHA,EAAK,EAAQ,QAAUC,EAAAA,QAAG,WAAA,CAAY,CAAe,IACrD,EAAQ,WAAW,GAAG,EAAgB,iCAAiC,EACvE,MAAO,EAAQ,SAAW,EAAA,CAAgB,EACnC,GACT"}
@@ -1,2 +1,2 @@
1
- import{createRequire as e}from"node:module";import t from"node:fs";import n from"node:path";import{globalDoomConfigDirectory as r}from"@agimon-ai/doompi-config";import{spawn as i}from"node:child_process";import a from"node:os";const o=[`dist`,`bin`,`cli.mjs`];function s(){let t=n.dirname(e(import.meta.url).resolve(`@agimon-ai/doompi/package.json`));return n.join(t,...o)}function c(){return new Promise((e,t)=>{let n=i(process.execPath,[s(),`init`],{cwd:a.homedir(),stdio:[`ignore`,`inherit`,`inherit`]});n.once(`error`,e=>t(Error(`doompi init could not start: ${e.message}`,{cause:e}))),n.once(`exit`,n=>{n===0?e():t(Error(`doompi init exited with code ${String(n)}`))})})}async function l(e={}){let n=e.homeDirectory??a.homedir(),i=r(n);return!(e.exists??t.existsSync)(i)&&(e.onNotice?.(`${i} is missing; running doompi init`),await(e.runInit??c)(),!0)}export{l as ensureDoomInitialized};
1
+ import{createRequire as e}from"node:module";import t from"node:fs";import n from"node:path";import r from"node:os";import{globalDoomConfigDirectory as i}from"@agimon-ai/doompi-config";import{spawn as a}from"node:child_process";const o=[`dist`,`bin`,`cli.mjs`];function s(){let t=n.dirname(e(import.meta.url).resolve(`@agimon-ai/doompi/package.json`));return n.join(t,...o)}function c(){return new Promise((e,t)=>{let n=a(process.execPath,[s(),`init`],{cwd:r.homedir(),stdio:[`ignore`,`inherit`,`inherit`]});n.once(`error`,e=>t(Error(`doompi init could not start: ${e.message}`,{cause:e}))),n.once(`exit`,n=>{n===0?e():t(Error(`doompi init exited with code ${String(n)}`))})})}async function l(e={}){let n=e.homeDirectory??r.homedir(),a=i(n);return!(e.exists??t.existsSync)(a)&&(e.onNotice?.(`${a} is missing; running doompi init`),await(e.runInit??c)(),!0)}export{l as ensureDoomInitialized};
2
2
  //# sourceMappingURL=doomInitialization.mjs.map
@@ -1,3 +1,3 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./piHubService.cjs"),n=require("./syncGuard.cjs"),r=require("./piWebSocketListener.cjs"),i=require("../types/media.cjs"),a=require("../services/fileMedia.cjs"),o=require("../services/staticAssets.cjs"),s=require("../types/hub.cjs"),c=require("../types/notification.cjs");require("../types/session.cjs");const l=require("./gitStatus.cjs"),u=require("./registryWatcher.cjs"),d=require("./serverSpawner.cjs"),f=require("./packageApiProxy.cjs"),p=require("./sessionHub.cjs"),m=require("../types/remoteAccess.cjs"),h=require("../services/remoteGuardPolicy.cjs"),g=require("../services/sessionMigration.cjs"),ee=require("./authRoutes.cjs"),te=require("./settingsRoutes.cjs"),ne=require("./providerAuth.cjs"),re=require("./remoteGuard.cjs"),ie=require("./remoteAccess.cjs"),ae=require("./bundlePublication.cjs"),oe=require("./webPush.cjs"),se=require("./remoteAccessStore.cjs"),ce=require("./remoteRoutes.cjs"),le=require("./tunnelProcess.cjs"),ue=require("./directoryListing.cjs"),de=require("./sessionFiles.cjs"),fe=require("./threadJournals.cjs"),pe=require("./webHubPluginLoader.cjs"),_=require("./webTelemetry.cjs"),me=require("./webComposition.cjs");let v=require("node:fs");v=e.__toESM(v,1);let y=require("node:path");y=e.__toESM(y,1);let b=require("node:url"),x=require("@agimon-ai/doompi/services"),S=require("node:crypto"),he=require("@hono/node-server"),ge=require("@hono/node-ws"),C=require("@earendil-works/pi-coding-agent"),w=require("@earendil-works/pi-server"),_e=require("@agimon-ai/doompi-config"),ve=require("@agimon-ai/doompi/services/syncState"),ye=require("@agimon-ai/doompi-web-security"),be=require("hono"),T=require("hono/cookie"),E=require("hono/body-limit"),D=require("@agimon-ai/doompi-extension-contracts/package-api"),xe=require("@agimon-ai/doompi-extension-contracts/package-api-loader"),Se=require("@agimon-ai/doompi-extension-contracts/sandbox-harness"),O=require("@agimon-ai/doompi/utils/repository");const k=`DOOMPI_WEB_PACKAGE_ROOT`,Ce=8388608,we=new Set([`GET`,`HEAD`,`POST`,`PUT`,`PATCH`,`DELETE`]),A=new Set([`connection`,`content-length`,`cookie`,`forwarded`,`host`,`origin`,`transfer-encoding`,`upgrade`,`x-forwarded-for`,`x-forwarded-host`,`x-forwarded-proto`]);function Te(e){if(typeof e!=`object`||!e)return;let t=e;if(t.v!==1||typeof t.method!=`string`||!we.has(t.method)||typeof t.target!=`string`||!t.target.startsWith(`/`)||t.target.startsWith(`//`)||t.target.includes(`#`)||!Array.isArray(t.headers)||!t.headers.every(e=>Array.isArray(e)&&e.length===2&&e.every(e=>typeof e==`string`))||t.body!==void 0&&typeof t.body!=`string`)return;let n=new URL(t.target,`http://sealed.doompi.invalid`);if(n.pathname!==m.REMOTE_HTTP_ROUTE&&n.pathname!==m.REMOTE_CHANNEL_ROUTE)return t}function Ee(e,t){try{let n=new Headers;for(let[t,r]of e.headers){let e=t.toLowerCase();A.has(e)||e.startsWith(`sec-`)||n.append(t,r)}for(let e of[`cookie`,`host`,`origin`]){let r=t.req.header(e);r!==void 0&&n.set(e,r)}return n}catch{return}}function De(e){if(e===void 0)return Buffer.alloc(0);if(!/^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$/.test(e))return;let t=Buffer.from(e,`base64`);return t.toString(`base64`)===e?t:void 0}function j(e){let t=process.env[k];if(t!==void 0&&t!==``)return y.default.join(t,`dist`,e);let n=y.default.dirname((0,b.fileURLToPath)(require("url").pathToFileURL(__filename).href));for(;;){if(v.default.existsSync(y.default.join(n,`package.json`)))return y.default.join(n,`dist`,e);let t=y.default.dirname(n);if(t===n)throw Error(`doompi-web could not locate its own package root.`);n=t}}function M(){return j(`web`)}function Oe(){return j(`pwa`)}function ke(){let e=process.env[k],t=e!==void 0&&e!==``?e:y.default.dirname((0,b.fileURLToPath)(require("url").pathToFileURL(__filename).href));for(;;){let e=y.default.join(t,`package.json`);if(v.default.existsSync(e))try{let t=JSON.parse(v.default.readFileSync(e,`utf8`)),n=P(t)?t.version:void 0;return typeof n==`string`?n:`unknown`}catch{return`unknown`}let n=y.default.dirname(t);if(n===t)return`unknown`;t=n}}function Ae(e,t,n){if(e!==void 0)return e;let r=process.env.DOOMPI_WEB_DIST;if(r!==void 0&&r!==``)return r;let i=t?.webDirectory;return i!=null&&v.default.existsSync(y.default.join(i,`index.html`))?(n(`serving the synced cockpit bundle from ${i}`),i):M()}function N(e){try{return v.default.statSync(e).isFile()?v.default.readFileSync(e):void 0}catch{return}}function P(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function je(e,t){let n=e.firstMessage.split(`
2
- `,1)[0]?.trim();return(e.name?.trim()||n||y.default.basename(t)||`untitled`).slice(0,80)}function F(e){return e instanceof Error?e.message:String(e)}function I(e,t){return`${e}\n${t}`}function Me(e,t,n,r){return p.createSessionHub({source:u.watchRegistry(e.registryDir,t),spawner:d.createServerSpawner({registryDir:e.registryDir,command:e.spawnCommand,onNotice:t}),readGit:l.readGitStatus,channels:n,telemetry:r,onNotice:t})}function L(e){return`repo-${(0,S.createHash)(`sha256`).update(e).digest(`base64url`).slice(0,24)}`}function R(e){let t=new Map,n=()=>{for(let[e,n]of t)t.set(e,{...n,repository:{...n.repository,active:!1}});for(let n of e.records())try{let e=v.default.realpathSync((0,O.findRepositoryRoot)(n.cwd));t.set(e,{repository:{id:L(e),path:e,name:y.default.basename(e)||e,active:!0},seenAt:Date.now()})}catch{}let n=[...t.entries()].filter(([,e])=>!e.repository.active).sort((e,t)=>t[1].seenAt-e[1].seenAt);for(let[e]of n.slice(12))t.delete(e);return[...t.values()].sort((e,t)=>Number(t.repository.active)-Number(e.repository.active)||t.seenAt-e.seenAt||e.repository.path.localeCompare(t.repository.path)).map(e=>e.repository)};return{list:n,resolve(e){return n().find(t=>t.id===e)?.path}}}function Ne(e,t,n,r,i){let a=[];for(let o of t){let t=`${D.DOOM_API_ROUTE_PREFIX}/${o.basePath}`,s;try{s=o.start({scope:`hub`,onNotice:n,resolveRepository:r,readRepositorySync:i})}catch(e){n(`hub API '${o.basePath}' did not start (${F(e)}); its routes stay unmounted`);continue}a.push(s),e.all(`${t}/*`,async e=>{if(e.req.query(`session`)!==void 0)return e.notFound();let r=new URL(e.req.url);r.pathname=r.pathname.slice(t.length)||`/`;try{return await s.fetch(new Request(r,e.req.raw))}catch(t){return n(`hub API '${o.basePath}' failed on ${r.pathname} (${F(t)})`),e.json({error:`The '${o.basePath}' API failed.`},500)}})}return a}function Pe(e,t,n,r){e.all(`${D.DOOM_API_ROUTE_PREFIX}/*`,async e=>{let i=e.req.query(s.API_SESSION_QUERY_PARAM);if(i===void 0)return e.notFound();let a=t.snapshot().find(e=>e.id===i);if(a===void 0)return e.json({error:`No session ${i}.`},404);if(a.apiSocketPath===void 0)return e.json({error:`Session ${i} serves no package API.`},404);let o=new URL(e.req.url);o.searchParams.delete(s.API_SESSION_QUERY_PARAM);let c=_.readTraceContext(e.req.raw.headers);try{return await r.runInSpan(`web.package_proxy`,{"operation.name":`web.package_proxy`,"http.method":e.req.method},async t=>await f.proxyToSocket({socketPath:a.apiSocketPath,path:`${o.pathname}${o.search}`,method:e.req.method,headers:e.req.raw.headers,body:e.req.raw.body,trace:_.forwardedTraceContext(t,c)}),c)}catch(t){return n(`session ${i} API is unreachable (${F(t)})`),e.json({error:`Session ${i} is not answering.`},502)}})}async function z(e){let l=e.host??`127.0.0.1`,d=e.onNotice??(()=>{}),f=_.createWebTelemetry(),p=se.createRemoteAccessStore({stateDir:e.remoteStateDir,onNotice:d}),b=e=>{try{return v.default.realpathSync((0,O.findRepositoryRoot)(e))}catch{return}},D=e=>{let t=b(e);if(t!==void 0)return t;let n=(0,_e.globalDoomConfigDirectory)();try{return(0,x.readSyncRegistration)(n)?.root}catch(e){d(`global cockpit composition is unreadable (${F(e)})`);return}},k=e.compositionDir===void 0?void 0:b(e.compositionDir);if(e.compositionDir!==void 0&&k===void 0)throw Error(`Cockpit composition directory is not inside a repository: ${e.compositionDir}`);let we=u.readRegistryRecords(e.registryDir,d).map(e=>D(e.cwd)).filter(e=>e!==void 0),A=[...new Set(k===void 0?we:[k])].sort((e,t)=>e.localeCompare(t)),j=new Map;for(let e of A)j.set(e,n.createSyncGuard({repoRoot:e,onNotice:d}));await Promise.all([...j.values()].map(async e=>await e.ensureSynced()));let M=async()=>await me.resolveWebComposition({repositoryRoots:A,stateDirectory:p.directory,onNotice:d}),L;try{L=await M()}catch(e){d(`the cockpit composition is unavailable (${F(e)}); serving the packaged bundle`),L=void 0}let z={assetsDir:Ae(e.assetsDir,L,d),apiDirectory:L?.apiDirectory},B=async e=>{let t=D(e);if(t===void 0)return;let r=j.get(t);if(r!==void 0){await r.ensureSynced();return}let i=n.createSyncGuard({repoRoot:t,onNotice:d});try{await i.ensureSynced()}finally{i.close()}},V=Me(e,d,await pe.loadHubChannels(z.assetsDir,d),f),H=fe.createThreadJournals({resolve:(e,t)=>V.threadJournal(e,t),onNotice:d}),U=new Set,W=(e,t)=>{for(let n of U)t&&!n.local||n.post(e)},G=new be.Hono,K=(0,ge.createNodeWebSocket)({app:G}),Fe,q=Oe(),J=ae.createBundlePublication({assetsDir:()=>z.assetsDir,stateDir:p.directory,onNotice:d}),Y;le.reapStaleTunnel(p.directory,d);let Ie=e.onHandover===void 0?void 0:t=>{let n=g.planSessionMigration(V.snapshot().map(e=>({id:e.id,cwd:e.cwd,...e.name===void 0?{}:{name:e.name}})),t.sandbox.workspaces);for(let e of g.describeStranded(n.stranded))d(e);for(let e of n.migrate){let t=V.stop(e.id);t.ok||d(`could not stop ${e.name??e.id} before the move: ${t.error}`)}e.onHandover?.({settings:t,sessions:n.migrate})},X=ie.createRemoteAccess({store:p,launchTunnel:e.remoteAccess?.launchTunnel??le.createTunnelLauncher({stateDir:p.directory,...e.cloudflaredPath===void 0?{}:{cloudflaredPath:e.cloudflaredPath},onNotice:d,onExit:e=>{d(`remote access: ${e}`),X.disable()}}),bindListener:async()=>await new Promise((e,t)=>{let n=(0,he.serve)({fetch:G.fetch,port:0,hostname:`127.0.0.1`,serverOptions:{maxHeaderSize:16384,headersTimeout:5e3,requestTimeout:1e4}},t=>{K.injectWebSocket(n),e({port:t.port,close:async()=>await new Promise(e=>{n.closeAllConnections?.(),n.close(()=>e())})})}),r=n;r.maxConnections=64,r.maxRequestsPerSocket=100,n.once(`error`,t)}),bundleTrust:()=>J.trust(),onDeviceDropped:e=>Y?.remove(e),onNotice:d,...Ie===void 0?{}:{requestHandover:Ie},contained:(0,Se.insideSandbox)(process.env),broadcastLocal:e=>W(e,!0),broadcastAll:e=>W(e,!1),...e.remoteAccess?.now===void 0?{}:{now:e.remoteAccess.now}});Y=oe.createLiveWebPush({stateDir:p.directory,isConnected:e=>X.isDeviceConnected(e),onNotice:d});let Le=V.onEvent(e=>{e.kind===`frame`&&c.parseDoomNotificationEntry(e.frame)!==void 0&&Y?.notify()}),Z=re.createRemoteGuard({loopbackPort:()=>Fe,tunnelPolicy:()=>X.tunnelPolicy(),authorize:e=>X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),trustedDevice:e=>e.env?.sealedDeviceId,channelReady:(e,t)=>X.channelFor(e,t)!==void 0,stepUp:{required:e=>X.stepUpRequired(e),verify:async(e,t,n)=>{if(typeof n!=`object`||!n)return!1;let r=n.ceremonyId,i=n.response;if(typeof r!=`string`)return!1;let a=e.env?.sealedDeviceId??X.authorize((0,T.getCookie)(e,`doompi_device`,`host`)),o=a===void 0?`local`:`device:${a}`;return await X.passkeys().finishStepUp(r,o,t,i)}},extraOrigins:h.allowedOriginsFromEnv(process.env.DOOMPI_WEB_ALLOW_ORIGIN)});G.use(`*`,Z.middleware),G.use(`*`,async(e,t)=>{let n=new URL(e.req.url).pathname;if(n===m.REMOTE_HTTP_ROUTE)return t();let r=_.requestOperation(n);if(r===void 0)return t();let i=performance.now();return await f.runInSpan(r,{"operation.name":r,"http.method":e.req.method},async()=>{await t(),await f.recordEvent(`web.api.request`,{"operation.name":r,"http.method":e.req.method,"http.status_code":e.res.status,duration_ms:Math.max(0,Math.round(performance.now()-i))})},_.readTraceContext(e.req.raw.headers))}),G.use(`*`,async(e,t)=>{if(Z.listenerOf(e)===`local`)return t();let n=Number(e.req.header(`content-length`));return Number.isFinite(n)&&n>Ce?e.json({error:`The tunnel request body is too large.`},413):t()}),ce.registerRemoteRoutes(G,{remote:X,listenerOf:e=>Z.listenerOf(e)});let Re=(0,E.bodyLimit)({maxSize:Ce,onError:e=>e.json({error:`The tunnel request body is too large.`},413)});G.use(`*`,async(e,t)=>Z.listenerOf(e)===`local`?t():await Re(e,t));let ze=(0,E.bodyLimit)({maxSize:32768,onError:e=>e.json({error:`The telemetry batch is too large.`},413)}),Be=_.createBrowserTelemetryRateLimit();G.post(_.WEB_TELEMETRY_ROUTE,ze,async e=>{if(!Be())return e.json({error:`Too many telemetry batches.`},429);let t;try{t=await e.req.json()}catch{return e.json({error:`The telemetry batch must be JSON.`},400)}let n=_.parseBrowserTelemetryBatch(t);if(n===void 0)return e.json({error:`The telemetry batch is invalid.`},400);for(let e of n){if(_.isBrowserErrorEvent(e)){await f.recordError(_.BROWSER_ERROR_EVENT,_.createBrowserError(e),{"browser.source":e.source,...e.session_id===void 0?{}:{"session.id":e.session_id},"failure.kind":`browser`},{includeException:!0});continue}await f.recordEvent(e.name,{...e.duration_ms===void 0?{}:{duration_ms:e.duration_ms},...e.count===void 0?{}:{count:e.count}})}return e.body(null,204)}),G.post(m.REMOTE_HTTP_ROUTE,async e=>{let t=X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`));if(t===void 0)return e.json({error:`This device is not paired.`},401);let n=X.channelFor(t,`http`);if(n===void 0)return e.json({error:`This device has no sealed HTTP channel.`},401);let r;try{r=await e.req.json()}catch{return e.json({error:`The sealed request envelope was malformed.`},400)}let i=n.open(r);if(!i.ok)return e.json({error:`The sealed request was refused: ${i.failure}.`},400);let a;try{a=JSON.parse(new TextDecoder().decode(i.plaintext))}catch{return e.json({error:`The sealed HTTP request was malformed.`},400)}let o=Te(a),s=o===void 0?void 0:Ee(o,e),c=o===void 0?void 0:De(o.body);if(o===void 0||s===void 0||c===void 0)return e.json({error:`The sealed HTTP request was invalid.`},400);let l=new URL(o.target,new URL(e.req.url).origin),u=await G.request(l,{method:o.method,headers:s,...o.method===`GET`||o.method===`HEAD`?{}:{body:Uint8Array.from(c).buffer}},{...e.env,sealedDeviceId:t}),d=Buffer.from(await u.arrayBuffer()),f=n.seal(new TextEncoder().encode(JSON.stringify({v:1,status:u.status,headers:Array.from(u.headers.entries()),body:d.toString(`base64`)})));return f.ok?e.json(f.envelope):e.json({error:`The sealed response failed: ${f.failure}.`},503)});let Ve=(0,E.bodyLimit)({maxSize:8192,onError:e=>e.json({error:`The Push subscription is too large.`},413)}),Q=e=>e.env?.sealedDeviceId;G.get(m.REMOTE_PUSH_KEY_ROUTE,e=>Q(e)===void 0?e.json({error:`A sealed paired device is required.`},401):e.json({publicKey:Y?.publicKey()},200,{"Cache-Control":`no-store`})),G.post(m.REMOTE_PUSH_ROUTE,Ve,async e=>{let t=Q(e);if(t===void 0)return e.json({error:`A sealed paired device is required.`},401);let n;try{n=await e.req.json()}catch{return e.json({error:`The Push subscription must be JSON.`},400)}return Y?.subscribe(t,n)===!0?e.body(null,204):e.json({error:`The Push subscription is invalid.`},400)}),G.delete(m.REMOTE_PUSH_ROUTE,e=>{let t=Q(e);return t===void 0?e.json({error:`A sealed paired device is required.`},401):(Y?.remove(t),e.body(null,204))});let He=r.createPiWebSocketListener({onError:e=>d(`protocol: ${e.message}`)}),Ue=r.createPiWebSocketListener({onError:e=>d(`protocol: ${e.message}`)}),We=t.createPiHubService({records:()=>V.records(),spawn:e=>V.create(e),onNotice:d}),Ge={...We,createSession:async()=>{throw new w.PiServerError(`not_implemented`,`Remote protocol clients cannot create sessions`)}},Ke=new w.PiServer(We,{listeners:[He],onError:e=>d(`protocol: ${e.message}`)}),qe=new w.PiServer(Ge,{listeners:[Ue],onError:e=>d(`protocol: ${e.message}`)});await Promise.all([Ke.start(),qe.start()]);let $=ne.createProviderAuth({runtime:e.authRuntime,onNotice:d});ee.registerAuthRoutes(G,$);let Je=R(V),Ye=()=>Je.list(),Xe=e=>Je.resolve(e),Ze=e=>{let t=Xe(e);if(t===void 0)return;let n=(0,x.readSyncDrift)({repoRoot:t}),r;try{r=(0,x.readSyncRegistration)(t)?(0,ve.readSyncState)(t):void 0}catch{r=void 0}return{fresh:n.fresh,reasons:[...n.reasons],...r===void 0?{}:{mcpProjection:r.fileState.mcpProjection}}};te.registerSettingsRoutes(G,{repositories:Ye,models:()=>$.listModels()});let Qe=z.apiDirectory,$e=Ne(G,Qe===void 0&&!process.env[xe.PACKAGE_API_DIR_ENV]?[]:await(0,xe.loadPackageApis)(`hub`,{apiDirectory:Qe,onNotice:d}),d,Xe,Ze);Pe(G,V,d,f),G.get(`/api/health`,e=>e.json({ok:!0,role:`hub`,protocol:1,sessions:V.snapshot().length,pid:process.pid,version:ke()})),G.post(s.SESSIONS_API_ROUTE,async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!P(t)||typeof t.cwd!=`string`||t.cwd===``)return e.json({error:`A cwd string is required.`},400);let n=typeof t.name==`string`&&t.name!==``?t.name:void 0;await B(t.cwd);let r=await V.create({cwd:t.cwd,name:n,trace:_.readTraceContext(e.req.raw.headers)});return r.ok?e.json({sessionId:r.sessionId},201):e.json({error:r.error},r.code===`invalid_request`?400:502)}),G.post(`${s.SESSIONS_API_ROUTE}/:sessionId/restart`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);n!==void 0&&await B(n.cwd);let r=await V.restart(t,_.readTraceContext(e.req.raw.headers));return r.ok?e.json({sessionId:r.sessionId},202):e.json({error:r.error},r.code===`invalid_request`?404:502)}),G.get(`${s.SESSIONS_API_ROUTE}/:sessionId/history`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=await C.SessionManager.list(n.cwd);return e.json({sessions:r.map(e=>({id:e.id,...e.name===void 0?{}:{name:e.name},firstMessage:e.firstMessage,createdAt:e.created.toISOString(),updatedAt:e.modified.toISOString(),messageCount:e.messageCount}))})}),G.post(`${s.SESSIONS_API_ROUTE}/:sessionId/resume`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r;try{r=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!P(r)||typeof r.targetSessionId!=`string`||r.targetSessionId===``)return e.json({error:`A targetSessionId string is required.`},400);let i=(await C.SessionManager.list(n.cwd)).find(e=>e.id===r.targetSessionId);if(!i)return e.json({error:`That Pi thread does not belong to this workspace.`},404);await B(n.cwd);let a=await V.resume(t,{sessionId:i.id,name:je(i,n.cwd)},_.readTraceContext(e.req.raw.headers));return a.ok?e.json({sessionId:a.sessionId},202):e.json({error:a.error},a.code===`invalid_request`?409:502)}),G.delete(`${s.SESSIONS_API_ROUTE}/:sessionId`,e=>{let t=e.req.param(`sessionId`),n=V.stop(t);return n.ok?e.json({sessionId:t},202):e.json({error:n.error},n.code===`unknown`?404:n.code===`self`?409:502)}),G.get(`/api/sessions/:sessionId/files`,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=e.req.query(`q`)??``,i=await de.listSessionFiles(n.cwd,r,20);return e.json({files:i})});let et=t=>{if(!(Z.listenerOf(t)===`local`||(0,Se.insideSandbox)(process.env)))return e.browseRoot??process.cwd()};G.get(s.DIRECTORIES_API_ROUTE,async e=>{let t=et(e),n=await ue.suggestDirectories(e.req.query(`q`)??``,{limit:12,...t===void 0?{}:{root:t}});return e.json({directories:n})}),G.get(i.SESSION_FILE_ROUTE,async e=>{let t=e.req.param(`sessionId`),n=V.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=e.req.query(`path`)??``,o=await de.readSessionFile(n.cwd,r,i.MAX_SESSION_FILE_BYTES);return o.status===`forbidden`?e.json({error:`The path leaves the session directory.`},403):o.status===`not-found`?e.json({error:`No such file.`},404):o.status===`too-large`?e.json({error:`The file is too large to preview.`},413):e.body(new Uint8Array(o.body),200,a.sessionFileHeaders(r))}),G.get(m.SESSION_SOCKET_ROUTE,K.upgradeWebSocket(e=>{let t=Z.listenerOf(e)===`local`,n=t?void 0:X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),r=n===void 0?void 0:X.channelFor(n,`session`),i=new Set,a=(0,S.randomUUID)(),o=new Map,l,u,f,p,h=e=>{for(let[t,n]of o)(e===void 0||n.sessionId===e)&&(o.delete(t),H.unsubscribe(n.sessionId,n.threadId))};return{onOpen(e,m){if(!t&&r===void 0){m.close(1008,`sealed session channel required`);return}let g=e=>{let n=JSON.stringify(e),i=n;if(!t){if(r===void 0)return;let e=r.seal(new TextEncoder().encode(n));if(!e.ok){m.close(1008,`sealed session channel failed`);return}i=JSON.stringify(e.envelope)}try{m.send(i)}catch{}};f={post:g,local:t},U.add(f),n!==void 0&&(p=X.trackSocket(n,(e,t)=>m.close(e,t))),g(s.hubHello(V.channelTypes())),g({type:s.SESSIONS_SNAPSHOT_TYPE,sessions:V.snapshot()}),l=V.onEvent(e=>{e.kind===`upsert`?g({type:s.SESSION_UPSERT_TYPE,session:e.session}):e.kind===`removed`?(i.delete(e.sessionId),h(e.sessionId),g({type:s.SESSION_REMOVED_TYPE,sessionId:e.sessionId})):e.kind===`channel`?(e.connectionId===a||e.connectionId===void 0&&i.has(e.sessionId))&&g({type:e.frameType,sessionId:e.sessionId,payload:e.payload}):(c.parseDoomNotificationEntry(e.frame)!==void 0||i.has(e.sessionId))&&g(s.sessionFrameEnvelope(e.sessionId,e.frame))}),u=H.onFrame(e=>{o.has(I(e.sessionId,e.threadId))&&g(s.threadFrameEnvelope(e.sessionId,e.threadId,e.frame))}),d(`browser attached`)},onMessage(e){if(typeof e.data!=`string`)return;let n;try{n=JSON.parse(e.data)}catch{return}if(!t){if(r===void 0)return;let e=r.open(n);if(!e.ok)return;try{n=JSON.parse(new TextDecoder().decode(e.plaintext))}catch{return}}if(!P(n)||typeof n.sessionId!=`string`)return;let c=n.sessionId;if(typeof n.type==`string`&&V.channelTypes().includes(n.type)&&(i.has(c)||V.channelReceivesWithoutSubscription(n.type))){V.receiveChannel(c,n.type,n.payload,a);return}if(n.type===`subscribe`){let e=V.backlog(c);if(!e)return;i.add(c),f?.post(e);for(let e of V.channelFrames(c))f?.post(e);return}if(n.type===`unsubscribe`){i.delete(c);return}if(n.type===`history_request`){let e=V.history(c,{...typeof n.before==`string`?{before:n.before}:{},...typeof n.limit==`number`?{limit:n.limit}:{}});if(!e)return;f?.post(e);return}if(n.type===`subscribe_thread`||n.type===`unsubscribe_thread`){if(typeof n.threadId!=`string`)return;let e=n.threadId,t=I(c,e);if(n.type===`unsubscribe_thread`){o.delete(t)&&H.unsubscribe(c,e);return}if(o.has(t)||V.backlog(c)===void 0)return;o.set(t,{sessionId:c,threadId:e}),f?.post(s.threadBacklog(c,e,H.subscribe(c,e)));return}if(n.type===`session_command`&&P(n.frame)){if(n.frame.type===`attach`)return;V.command(c,n.frame)}},onClose(){V.disconnectChannels(a),f&&U.delete(f),f=void 0,p?.(),p=void 0,l?.(),l=void 0,u?.(),u=void 0,i.clear(),h(),d(`browser detached`)}}})),G.get(m.PROTOCOL_SOCKET_ROUTE,K.upgradeWebSocket(e=>{let t=Z.listenerOf(e)===`local`,n=t?void 0:X.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),r=n===void 0?void 0:X.channelFor(n,`protocol`),i,a;return{onOpen(e,o){if(!t&&r===void 0){o.close(1008,`sealed protocol channel required`);return}n!==void 0&&(a=X.trackSocket(n,(e,t)=>o.close(e,t))),i=(t?He:Ue).accept({send:e=>{if(t){o.send(e);return}if(r===void 0)return;let n=e instanceof Uint8Array?e:new Uint8Array(e),i=r.seal(n);if(!i.ok){o.close(1008,`sealed protocol channel failed`);return}o.send(new TextEncoder().encode(JSON.stringify(i.envelope)))},close:()=>o.close(),get readyState(){return o.readyState}})},async onMessage(e){let n=e.data;if(typeof n==`string`)return;let a=n instanceof Blob?new Uint8Array(await n.arrayBuffer()):(n instanceof ArrayBuffer,new Uint8Array(n));if(t){i?.onData(a);return}if(r===void 0)return;let o;try{o=JSON.parse(new TextDecoder().decode(a))}catch{return}let s=r.open(o);s.ok&&i?.onData(s.plaintext)},onClose(){a?.(),a=void 0,i?.onClose(),i=void 0},onError(e){i?.onError(e instanceof Error?e:Error(`The protocol socket failed`)),i=void 0}}})),G.get(`/manifest.webmanifest`,e=>{let t=N(y.default.join(q,`manifest.webmanifest`));return t===void 0?e.text(`The PWA manifest is unavailable.`,404):e.body(new Uint8Array(t),200,{"Cache-Control":`no-store`,"Content-Type":`application/manifest+json; charset=utf-8`})}),G.get(`/sw.js`,e=>{let t=N(y.default.join(q,`sw.js`));return t===void 0?e.text(`The trusted service worker is unavailable.`,404):e.body(new Uint8Array(t),200,{"Cache-Control":`no-cache`,"Content-Type":`text/javascript; charset=utf-8`,"Service-Worker-Allowed":`/`})}),G.get(`/pwa/*`,e=>{let t=new URL(e.req.url).pathname.slice(5),n=o.resolveAssetPath(q,`/${t}`),r=n===void 0?void 0:N(n);return n===void 0||r===void 0?e.text(`PWA asset not found.`,404):e.body(new Uint8Array(r),200,{"Cache-Control":`no-store`,"Content-Type":o.contentTypeFor(n),"X-Content-Type-Options":`nosniff`})}),G.get(ye.BUNDLE_MANIFEST_ROUTE,e=>{let t=J.current();return t===void 0?e.json({error:`No signed cockpit bundle is available.`},503):e.json(t.signed,200,{"Cache-Control":`no-store`})}),G.get(`/bundle-assets/*`,e=>{let t=new URL(e.req.url).pathname,n=/^\/bundle-assets\/([1-9][0-9]*)(\/.*)$/u.exec(t),r=n===null?void 0:Number(n[1]),i=J.current();if(n===null||!Number.isSafeInteger(r)||i===void 0||i.signed.manifest.revision!==r)return e.text(`That signed bundle revision is unavailable.`,404);let a=(0,ye.assetFor)(i.signed.manifest,n[2]??``),s=a===void 0?void 0:o.resolveAssetPath(i.assetsDir,a.path),c=s===void 0?void 0:N(s);return a===void 0||s===void 0||c===void 0?e.text(`Bundle asset not found.`,404):e.body(new Uint8Array(c),200,{"Cache-Control":`no-store`,"Content-Length":String(c.byteLength),"Content-Type":a.contentType,"X-Content-Type-Options":`nosniff`})}),G.get(`/`,e=>e.redirect(m.PAIRING_PAGE_ROUTE)),G.get(`*`,e=>{let t=new URL(e.req.url).pathname,n=e.req.header(`accept`)?.includes(`text/html`)===!0;return!t.startsWith(`/api/`)&&n?e.redirect(`/`):e.text(`Not found.`,404)});let tt=async()=>{try{let e=await M();e!==void 0&&e.webDirectory!==z.assetsDir&&(z={assetsDir:e.webDirectory,apiDirectory:e.apiDirectory},d(`serving the rebuilt cockpit bundle from ${z.assetsDir}`)),J.refresh(),W({type:s.HUB_RESYNCED_TYPE},!1)}catch(e){d(`the rebuilt cockpit composition is unavailable (${F(e)}); keeping the served bundle`)}};for(let e of j.values())e.watch(()=>void tt());return new Promise((t,n)=>{let r=(0,he.serve)({fetch:G.fetch,port:e.port,hostname:l},e=>{Fe=e.port,K.injectWebSocket(r);let n=`http://${l}:${e.port}`;d(`cockpit on ${n}`);let i,a=async()=>{await X.close(),await new Promise(e=>{H.close();for(let e of j.values())e.close();Ke.close(),qe.close(),Le(),Y?.close(),V.close(),$.close();for(let e of $e)e.close();for(let e of K.wss.clients)e.terminate();K.wss.close(),r.closeAllConnections?.(),r.close(()=>e())}),await _.shutdownWebTelemetry(f)};t({url:n,port:e.port,close:()=>i??=a()})});r.once(`error`,e=>{H.close(),Le(),Y?.close(),V.close(),$.close(),X.close();for(let e of $e)e.close();f.shutdown(),n(e)})})}exports.createSettingsRepositoryRegistry=R,exports.packagedVersion=ke,exports.serveWeb=z;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("./piHubService.cjs"),n=require("./syncGuard.cjs"),r=require("./piWebSocketListener.cjs"),i=require("../types/media.cjs"),a=require("../services/fileMedia.cjs"),o=require("../services/staticAssets.cjs"),s=require("../types/hub.cjs"),c=require("../types/notification.cjs");require("../types/session.cjs");const l=require("./gitStatus.cjs"),u=require("./registryWatcher.cjs"),d=require("./serverSpawner.cjs"),f=require("./packageApiProxy.cjs"),p=require("./sessionHub.cjs"),m=require("../types/remoteAccess.cjs"),h=require("../services/remoteGuardPolicy.cjs"),g=require("../services/sessionMigration.cjs"),ee=require("./authRoutes.cjs"),te=require("./settingsRoutes.cjs"),ne=require("./providerAuth.cjs"),re=require("./remoteGuard.cjs"),ie=require("./remoteAccess.cjs"),ae=require("./bundlePublication.cjs"),oe=require("./webPush.cjs"),se=require("./remoteAccessStore.cjs"),ce=require("./remoteRoutes.cjs"),le=require("./tunnelProcess.cjs"),ue=require("./directoryListing.cjs"),de=require("./sessionFiles.cjs"),fe=require("./threadJournals.cjs"),pe=require("./webHubPluginLoader.cjs"),_=require("./webTelemetry.cjs"),me=require("./sessionWebComposition.cjs");let v=require("node:fs");v=e.__toESM(v,1);let y=require("node:path");y=e.__toESM(y,1);let b=require("node:url"),x=require("@agimon-ai/doompi/services"),he=require("node:crypto"),S=require("node:os");S=e.__toESM(S,1);let ge=require("@hono/node-server"),_e=require("@hono/node-ws"),ve=require("@earendil-works/pi-coding-agent"),C=require("@earendil-works/pi-server"),ye=require("@agimon-ai/doompi-config"),be=require("@agimon-ai/doompi/services/syncState"),w=require("@agimon-ai/doompi-web-security"),xe=require("hono"),T=require("hono/cookie"),E=require("hono/body-limit"),D=require("@agimon-ai/doompi-extension-contracts/package-api"),Se=require("@agimon-ai/doompi-extension-contracts/package-api-loader"),Ce=require("@agimon-ai/doompi-extension-contracts/sandbox-harness"),O=require("@agimon-ai/doompi/utils/repository");const k=`DOOMPI_WEB_PACKAGE_ROOT`,A=`/api/web-plugins/`,we=8388608,j=new Set([`GET`,`HEAD`,`POST`,`PUT`,`PATCH`,`DELETE`]),M=new Set([`connection`,`content-length`,`cookie`,`forwarded`,`host`,`origin`,`transfer-encoding`,`upgrade`,`x-forwarded-for`,`x-forwarded-host`,`x-forwarded-proto`]);function Te(e){if(typeof e!=`object`||!e)return;let t=e;if(t.v!==1||typeof t.method!=`string`||!j.has(t.method)||typeof t.target!=`string`||!t.target.startsWith(`/`)||t.target.startsWith(`//`)||t.target.includes(`#`)||!Array.isArray(t.headers)||!t.headers.every(e=>Array.isArray(e)&&e.length===2&&e.every(e=>typeof e==`string`))||t.body!==void 0&&typeof t.body!=`string`)return;let n=new URL(t.target,`http://sealed.doompi.invalid`);if(n.pathname!==m.REMOTE_HTTP_ROUTE&&n.pathname!==m.REMOTE_CHANNEL_ROUTE)return t}function Ee(e,t){try{let n=new Headers;for(let[t,r]of e.headers){let e=t.toLowerCase();M.has(e)||e.startsWith(`sec-`)||n.append(t,r)}for(let e of[`cookie`,`host`,`origin`]){let r=t.req.header(e);r!==void 0&&n.set(e,r)}return n}catch{return}}function De(e){if(e===void 0)return Buffer.alloc(0);if(!/^(?:[A-Za-z\d+/]{4})*(?:[A-Za-z\d+/]{2}==|[A-Za-z\d+/]{3}=)?$/.test(e))return;let t=Buffer.from(e,`base64`);return t.toString(`base64`)===e?t:void 0}function N(e){let t=process.env[k];if(t!==void 0&&t!==``)return y.default.join(t,`dist`,e);let n=y.default.dirname((0,b.fileURLToPath)(require("url").pathToFileURL(__filename).href));for(;;){if(v.default.existsSync(y.default.join(n,`package.json`)))return y.default.join(n,`dist`,e);let t=y.default.dirname(n);if(t===n)throw Error(`doompi-web could not locate its own package root.`);n=t}}function P(){return N(`web`)}function Oe(){return N(`pwa`)}function ke(){let e=process.env[k],t=e!==void 0&&e!==``?e:y.default.dirname((0,b.fileURLToPath)(require("url").pathToFileURL(__filename).href));for(;;){let e=y.default.join(t,`package.json`);if(v.default.existsSync(e))try{let t=JSON.parse(v.default.readFileSync(e,`utf8`)),n=I(t)?t.version:void 0;return typeof n==`string`?n:`unknown`}catch{return`unknown`}let n=y.default.dirname(t);if(n===t)return`unknown`;t=n}}function Ae(e,t,n){if(e!==void 0)return e;let r=process.env.DOOMPI_WEB_DIST;if(r!==void 0&&r!==``)return r;let i=t?.webDirectory;return i!=null&&v.default.existsSync(y.default.join(i,`index.html`))?(n(`serving the synced cockpit bundle from ${i}`),i):P()}function F(e){try{return v.default.statSync(e).isFile()?v.default.readFileSync(e):void 0}catch{return}}function I(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function je(e,t){let n=e.firstMessage.split(`
2
+ `,1)[0]?.trim();return(e.name?.trim()||n||y.default.basename(t)||`untitled`).slice(0,80)}function L(e){return e instanceof Error?e.message:String(e)}function Me(e,t){return`${e}\n${t}`}function Ne(e,t,n,r){return p.createSessionHub({source:u.watchRegistry(e.registryDir,t),spawner:d.createServerSpawner({registryDir:e.registryDir,command:e.spawnCommand,onNotice:t}),readGit:l.readGitStatus,...n,telemetry:r,onNotice:t})}function R(e){return`repo-${(0,he.createHash)(`sha256`).update(e).digest(`base64url`).slice(0,24)}`}function Pe(e){let t=new Map,n=()=>{for(let[e,n]of t)t.set(e,{...n,repository:{...n.repository,active:!1}});for(let n of e.records())try{let e=v.default.realpathSync((0,O.findRepositoryRoot)(n.cwd));t.set(e,{repository:{id:R(e),path:e,name:y.default.basename(e)||e,active:!0},seenAt:Date.now()})}catch{}let n=[...t.entries()].filter(([,e])=>!e.repository.active).sort((e,t)=>t[1].seenAt-e[1].seenAt);for(let[e]of n.slice(12))t.delete(e);return[...t.values()].sort((e,t)=>Number(t.repository.active)-Number(e.repository.active)||t.seenAt-e.seenAt||e.repository.path.localeCompare(t.repository.path)).map(e=>e.repository)};return{list:n,resolve(e){return n().find(t=>t.id===e)?.path}}}function Fe(e,t,n,r,i){let a=[];for(let o of t){let t=`${D.DOOM_API_ROUTE_PREFIX}/${o.basePath}`,s;try{s=o.start({scope:`hub`,onNotice:n,resolveRepository:r,readRepositorySync:i})}catch(e){n(`hub API '${o.basePath}' did not start (${L(e)}); its routes stay unmounted`);continue}a.push(s),e.all(`${t}/*`,async e=>{if(e.req.query(`session`)!==void 0)return e.notFound();let r=new URL(e.req.url);r.pathname=r.pathname.slice(t.length)||`/`;try{return await s.fetch(new Request(r,e.req.raw))}catch(t){return n(`hub API '${o.basePath}' failed on ${r.pathname} (${L(t)})`),e.json({error:`The '${o.basePath}' API failed.`},500)}})}return a}function Ie(e,t,n,r){e.all(`${D.DOOM_API_ROUTE_PREFIX}/*`,async e=>{let i=e.req.query(s.API_SESSION_QUERY_PARAM);if(i===void 0)return e.notFound();let a=t.snapshot().find(e=>e.id===i);if(a===void 0)return e.json({error:`No session ${i}.`},404);if(a.apiSocketPath===void 0)return e.json({error:`Session ${i} serves no package API.`},404);let o=new URL(e.req.url);o.searchParams.delete(s.API_SESSION_QUERY_PARAM);let c=_.readTraceContext(e.req.raw.headers);try{return await r.runInSpan(`web.package_proxy`,{"operation.name":`web.package_proxy`,"http.method":e.req.method},async t=>await f.proxyToSocket({socketPath:a.apiSocketPath,path:`${o.pathname}${o.search}`,method:e.req.method,headers:e.req.raw.headers,body:e.req.raw.body,trace:_.forwardedTraceContext(t,c)}),c)}catch(t){return n(`session ${i} API is unreachable (${L(t)})`),e.json({error:`Session ${i} is not answering.`},502)}})}async function z(e){let l=e.host??`127.0.0.1`,d=e.onNotice??(()=>{}),f=_.createWebTelemetry(),p=se.createRemoteAccessStore({stateDir:e.remoteStateDir,onNotice:d}),b=e=>{try{return v.default.realpathSync((0,O.findRepositoryRoot)(e))}catch{return}},D=e=>{let t=S.default.homedir(),n=(0,O.resolveDoomConfigurationRoot)(e,t);if(n===(0,ye.globalDoomConfigDirectory)(t))return n;try{return v.default.realpathSync(n)}catch{return y.default.resolve(n)}},k=e.compositionDir===void 0?void 0:b(e.compositionDir);if(e.compositionDir!==void 0&&k===void 0)throw Error(`Cockpit composition directory is not inside a repository: ${e.compositionDir}`);let j=(0,ye.globalDoomConfigDirectory)(S.default.homedir()),M=n.createSyncGuard({repoRoot:j,onNotice:d});await M.ensureSynced();let N=async e=>{if(e===j){await M.ensureSynced();return}let t=n.createSyncGuard({repoRoot:e,onNotice:d});try{await t.ensureSynced()}finally{t.close()}},P=async e=>await N(D(e)),L=[...u.readRegistryRecords(e.registryDir,d).map(e=>D(e.cwd)),...k===void 0?[]:[D(k)]];await Promise.all([...new Set(L)].map(async e=>await N(e)));let R={assetsDir:Ae(e.assetsDir,void 0,d),apiDirectory:void 0},z=ae.createPluginBundlePublication(p.directory,d),B=Ne(e,d,{loadChannels:async e=>{let t=D(e.cwd);await N(t);let n=(0,x.readSyncRegistration)(t);return n?.webDirectory===null||n?.webDirectory===void 0?[]:await pe.loadHubChannels(n.webDirectory,d)},webComposition:(e,t)=>{let n=me.resolveSessionWebArtifacts(D(e.cwd));if(n===void 0)return;let r=z.publish(n.id,n.pluginsDir);if(r===void 0)return;let i=r.signed.manifest.revision,a=`${A}${n.id}/${String(i)}`;return{id:n.id,revision:i,manifestUrl:`${a}/manifest`,rawAssetBaseUrl:`${a}/assets`,verifiedAssetBaseUrl:`/verified-plugins/${n.id}/${String(i)}`,entryPath:n.entryPath,stylePaths:n.stylePaths,channels:[...t]}}},f),V=fe.createThreadJournals({resolve:(e,t)=>B.threadJournal(e,t),onNotice:d}),H=new Set,Le=(e,t)=>{for(let n of H)t&&!n.local||n.post(e)},U=new xe.Hono,W=(0,_e.createNodeWebSocket)({app:U}),Re,G=Oe(),K=ae.createBundlePublication({assetsDir:()=>R.assetsDir,stateDir:p.directory,onNotice:d}),q;le.reapStaleTunnel(p.directory,d);let ze=e.onHandover===void 0?void 0:t=>{let n=g.planSessionMigration(B.snapshot().map(e=>({id:e.id,cwd:e.cwd,...e.name===void 0?{}:{name:e.name}})),t.sandbox.workspaces);for(let e of g.describeStranded(n.stranded))d(e);for(let e of n.migrate){let t=B.stop(e.id);t.ok||d(`could not stop ${e.name??e.id} before the move: ${t.error}`)}e.onHandover?.({settings:t,sessions:n.migrate})},J=ie.createRemoteAccess({store:p,launchTunnel:e.remoteAccess?.launchTunnel??le.createTunnelLauncher({stateDir:p.directory,...e.cloudflaredPath===void 0?{}:{cloudflaredPath:e.cloudflaredPath},onNotice:d,onExit:e=>{d(`remote access: ${e}`),J.disable()}}),bindListener:async()=>await new Promise((e,t)=>{let n=(0,ge.serve)({fetch:U.fetch,port:0,hostname:`127.0.0.1`,serverOptions:{maxHeaderSize:16384,headersTimeout:5e3,requestTimeout:1e4}},t=>{W.injectWebSocket(n),e({port:t.port,close:async()=>await new Promise(e=>{n.closeAllConnections?.(),n.close(()=>e())})})}),r=n;r.maxConnections=64,r.maxRequestsPerSocket=100,n.once(`error`,t)}),bundleTrust:()=>K.trust(),onDeviceDropped:e=>q?.remove(e),onNotice:d,...ze===void 0?{}:{requestHandover:ze},contained:(0,Ce.insideSandbox)(process.env),broadcastLocal:e=>Le(e,!0),broadcastAll:e=>Le(e,!1),...e.remoteAccess?.now===void 0?{}:{now:e.remoteAccess.now}});q=oe.createLiveWebPush({stateDir:p.directory,isConnected:e=>J.isDeviceConnected(e),onNotice:d});let Be=B.onEvent(e=>{e.kind===`frame`&&c.parseDoomNotificationEntry(e.frame)!==void 0&&q?.notify()}),Y=re.createRemoteGuard({loopbackPort:()=>Re,tunnelPolicy:()=>J.tunnelPolicy(),authorize:e=>J.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),trustedDevice:e=>e.env?.sealedDeviceId,channelReady:(e,t)=>J.channelFor(e,t)!==void 0,stepUp:{required:e=>J.stepUpRequired(e),verify:async(e,t,n)=>{if(typeof n!=`object`||!n)return!1;let r=n.ceremonyId,i=n.response;if(typeof r!=`string`)return!1;let a=e.env?.sealedDeviceId??J.authorize((0,T.getCookie)(e,`doompi_device`,`host`)),o=a===void 0?`local`:`device:${a}`;return await J.passkeys().finishStepUp(r,o,t,i)}},extraOrigins:h.allowedOriginsFromEnv(process.env.DOOMPI_WEB_ALLOW_ORIGIN)});U.use(`*`,Y.middleware),U.use(`*`,async(e,t)=>{let n=new URL(e.req.url).pathname;if(n===m.REMOTE_HTTP_ROUTE)return t();let r=_.requestOperation(n);if(r===void 0)return t();let i=performance.now();return await f.runInSpan(r,{"operation.name":r,"http.method":e.req.method},async()=>{await t(),await f.recordEvent(`web.api.request`,{"operation.name":r,"http.method":e.req.method,"http.status_code":e.res.status,duration_ms:Math.max(0,Math.round(performance.now()-i))})},_.readTraceContext(e.req.raw.headers))}),U.use(`*`,async(e,t)=>{if(Y.listenerOf(e)===`local`)return t();let n=Number(e.req.header(`content-length`));return Number.isFinite(n)&&n>we?e.json({error:`The tunnel request body is too large.`},413):t()}),ce.registerRemoteRoutes(U,{remote:J,listenerOf:e=>Y.listenerOf(e)});let Ve=(0,E.bodyLimit)({maxSize:we,onError:e=>e.json({error:`The tunnel request body is too large.`},413)});U.use(`*`,async(e,t)=>Y.listenerOf(e)===`local`?t():await Ve(e,t));let He=(0,E.bodyLimit)({maxSize:32768,onError:e=>e.json({error:`The telemetry batch is too large.`},413)}),Ue=_.createBrowserTelemetryRateLimit();U.post(_.WEB_TELEMETRY_ROUTE,He,async e=>{if(!Ue())return e.json({error:`Too many telemetry batches.`},429);let t;try{t=await e.req.json()}catch{return e.json({error:`The telemetry batch must be JSON.`},400)}let n=_.parseBrowserTelemetryBatch(t);if(n===void 0)return e.json({error:`The telemetry batch is invalid.`},400);for(let e of n){if(_.isBrowserErrorEvent(e)){await f.recordError(_.BROWSER_ERROR_EVENT,_.createBrowserError(e),{"browser.source":e.source,...e.session_id===void 0?{}:{"session.id":e.session_id},"failure.kind":`browser`},{includeException:!0});continue}await f.recordEvent(e.name,{...e.duration_ms===void 0?{}:{duration_ms:e.duration_ms},...e.count===void 0?{}:{count:e.count}})}return e.body(null,204)}),U.post(m.REMOTE_HTTP_ROUTE,async e=>{let t=J.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`));if(t===void 0)return e.json({error:`This device is not paired.`},401);let n=J.channelFor(t,`http`);if(n===void 0)return e.json({error:`This device has no sealed HTTP channel.`},401);let r;try{r=await e.req.json()}catch{return e.json({error:`The sealed request envelope was malformed.`},400)}let i=n.open(r);if(!i.ok)return e.json({error:`The sealed request was refused: ${i.failure}.`},400);let a;try{a=JSON.parse(new TextDecoder().decode(i.plaintext))}catch{return e.json({error:`The sealed HTTP request was malformed.`},400)}let o=Te(a),s=o===void 0?void 0:Ee(o,e),c=o===void 0?void 0:De(o.body);if(o===void 0||s===void 0||c===void 0)return e.json({error:`The sealed HTTP request was invalid.`},400);let l=new URL(o.target,new URL(e.req.url).origin),u=await U.request(l,{method:o.method,headers:s,...o.method===`GET`||o.method===`HEAD`?{}:{body:Uint8Array.from(c).buffer}},{...e.env,sealedDeviceId:t}),d=Buffer.from(await u.arrayBuffer()),f=n.seal(new TextEncoder().encode(JSON.stringify({v:1,status:u.status,headers:Array.from(u.headers.entries()),body:d.toString(`base64`)})));return f.ok?e.json(f.envelope):e.json({error:`The sealed response failed: ${f.failure}.`},503)});let We=(0,E.bodyLimit)({maxSize:8192,onError:e=>e.json({error:`The Push subscription is too large.`},413)}),X=e=>e.env?.sealedDeviceId;U.get(m.REMOTE_PUSH_KEY_ROUTE,e=>X(e)===void 0?e.json({error:`A sealed paired device is required.`},401):e.json({publicKey:q?.publicKey()},200,{"Cache-Control":`no-store`})),U.post(m.REMOTE_PUSH_ROUTE,We,async e=>{let t=X(e);if(t===void 0)return e.json({error:`A sealed paired device is required.`},401);let n;try{n=await e.req.json()}catch{return e.json({error:`The Push subscription must be JSON.`},400)}return q?.subscribe(t,n)===!0?e.body(null,204):e.json({error:`The Push subscription is invalid.`},400)}),U.delete(m.REMOTE_PUSH_ROUTE,e=>{let t=X(e);return t===void 0?e.json({error:`A sealed paired device is required.`},401):(q?.remove(t),e.body(null,204))});let Ge=r.createPiWebSocketListener({onError:e=>d(`protocol: ${e.message}`)}),Ke=r.createPiWebSocketListener({onError:e=>d(`protocol: ${e.message}`)}),qe=t.createPiHubService({records:()=>B.records(),spawn:e=>B.create(e),onNotice:d}),Je={...qe,createSession:async()=>{throw new C.PiServerError(`not_implemented`,`Remote protocol clients cannot create sessions`)}},Z=new C.PiServer(qe,{listeners:[Ge],onError:e=>d(`protocol: ${e.message}`)}),Ye=new C.PiServer(Je,{listeners:[Ke],onError:e=>d(`protocol: ${e.message}`)});await Promise.all([Z.start(),Ye.start()]);let Q=ne.createProviderAuth({runtime:e.authRuntime,onNotice:d});ee.registerAuthRoutes(U,Q);let Xe=Pe(B),Ze=()=>Xe.list(),Qe=e=>Xe.resolve(e),$e=e=>{let t=Qe(e);if(t===void 0)return;let n=(0,x.readSyncDrift)({repoRoot:t}),r;try{r=(0,x.readSyncRegistration)(t)?(0,be.readSyncState)(t):void 0}catch{r=void 0}return{fresh:n.fresh,reasons:[...n.reasons],...r===void 0?{}:{mcpProjection:r.fileState.mcpProjection}}};te.registerSettingsRoutes(U,{repositories:Ze,models:()=>Q.listModels()});let et=R.apiDirectory,$=Fe(U,et===void 0&&!process.env[Se.PACKAGE_API_DIR_ENV]?[]:await(0,Se.loadPackageApis)(`hub`,{apiDirectory:et,onNotice:d}),d,Qe,$e);Ie(U,B,d,f),U.get(`/api/health`,e=>e.json({ok:!0,role:`hub`,protocol:2,sessions:B.snapshot().length,pid:process.pid,version:ke()})),U.post(s.SESSIONS_API_ROUTE,async e=>{let t;try{t=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!I(t)||typeof t.cwd!=`string`||t.cwd===``)return e.json({error:`A cwd string is required.`},400);let n;try{n=v.default.statSync(t.cwd)}catch{return e.json({error:`No such directory: ${t.cwd}`},400)}if(!y.default.isAbsolute(t.cwd)||!n.isDirectory())return e.json({error:`The working directory must be an absolute path to a directory, received "${t.cwd}".`},400);let r=typeof t.name==`string`&&t.name!==``?t.name:void 0;await P(t.cwd);let i=await B.create({cwd:t.cwd,name:r,trace:_.readTraceContext(e.req.raw.headers)});return i.ok?e.json({sessionId:i.sessionId},201):e.json({error:i.error},i.code===`invalid_request`?400:502)}),U.post(`${s.SESSIONS_API_ROUTE}/:sessionId/restart`,async e=>{let t=e.req.param(`sessionId`),n=B.snapshot().find(e=>e.id===t);n!==void 0&&await P(n.cwd);let r=await B.restart(t,_.readTraceContext(e.req.raw.headers));return r.ok?e.json({sessionId:r.sessionId},202):e.json({error:r.error},r.code===`invalid_request`?404:502)}),U.get(`${s.SESSIONS_API_ROUTE}/:sessionId/history`,async e=>{let t=e.req.param(`sessionId`),n=B.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=await ve.SessionManager.list(n.cwd);return e.json({sessions:r.map(e=>({id:e.id,...e.name===void 0?{}:{name:e.name},firstMessage:e.firstMessage,createdAt:e.created.toISOString(),updatedAt:e.modified.toISOString(),messageCount:e.messageCount}))})}),U.post(`${s.SESSIONS_API_ROUTE}/:sessionId/resume`,async e=>{let t=e.req.param(`sessionId`),n=B.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r;try{r=await e.req.json()}catch{return e.json({error:`The request body must be JSON.`},400)}if(!I(r)||typeof r.targetSessionId!=`string`||r.targetSessionId===``)return e.json({error:`A targetSessionId string is required.`},400);let i=(await ve.SessionManager.list(n.cwd)).find(e=>e.id===r.targetSessionId);if(!i)return e.json({error:`That Pi thread does not belong to this workspace.`},404);await P(n.cwd);let a=await B.resume(t,{sessionId:i.id,name:je(i,n.cwd)},_.readTraceContext(e.req.raw.headers));return a.ok?e.json({sessionId:a.sessionId},202):e.json({error:a.error},a.code===`invalid_request`?409:502)}),U.delete(`${s.SESSIONS_API_ROUTE}/:sessionId`,e=>{let t=e.req.param(`sessionId`),n=B.stop(t);return n.ok?e.json({sessionId:t},202):e.json({error:n.error},n.code===`unknown`?404:n.code===`self`?409:502)}),U.get(`/api/sessions/:sessionId/files`,async e=>{let t=e.req.param(`sessionId`),n=B.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=e.req.query(`q`)??``,i=await de.listSessionFiles(n.cwd,r,20);return e.json({files:i})});let tt=t=>{if(!(Y.listenerOf(t)===`local`||(0,Ce.insideSandbox)(process.env)))return e.browseRoot??process.cwd()};return U.get(s.DIRECTORIES_API_ROUTE,async e=>{let t=tt(e),n=await ue.suggestDirectories(e.req.query(`q`)??``,{limit:12,...t===void 0?{}:{root:t}});return e.json({directories:n})}),U.get(i.SESSION_FILE_ROUTE,async e=>{let t=e.req.param(`sessionId`),n=B.snapshot().find(e=>e.id===t);if(!n)return e.json({error:`Unknown session.`},404);let r=e.req.query(`path`)??``,o=await de.readSessionFile(n.cwd,r,i.MAX_SESSION_FILE_BYTES);return o.status===`forbidden`?e.json({error:`The path leaves the session directory.`},403):o.status===`not-found`?e.json({error:`No such file.`},404):o.status===`too-large`?e.json({error:`The file is too large to preview.`},413):e.body(new Uint8Array(o.body),200,a.sessionFileHeaders(r))}),U.get(m.SESSION_SOCKET_ROUTE,W.upgradeWebSocket(e=>{let t=Y.listenerOf(e)===`local`,n=t?void 0:J.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),r=n===void 0?void 0:J.channelFor(n,`session`),i=new Set,a=(0,he.randomUUID)(),o=new Map,l,u,f,p,h=e=>{for(let[t,n]of o)(e===void 0||n.sessionId===e)&&(o.delete(t),V.unsubscribe(n.sessionId,n.threadId))};return{onOpen(e,m){if(!t&&r===void 0){m.close(1008,`sealed session channel required`);return}let g=e=>{let n=JSON.stringify(e),i=n;if(!t){if(r===void 0)return;let e=r.seal(new TextEncoder().encode(n));if(!e.ok){m.close(1008,`sealed session channel failed`);return}i=JSON.stringify(e.envelope)}try{m.send(i)}catch{}};f={post:g,local:t},H.add(f),n!==void 0&&(p=J.trackSocket(n,(e,t)=>m.close(e,t))),g(s.hubHello(B.channelTypes())),g({type:s.SESSIONS_SNAPSHOT_TYPE,sessions:B.snapshot()}),l=B.onEvent(e=>{e.kind===`upsert`?g({type:s.SESSION_UPSERT_TYPE,session:e.session}):e.kind===`removed`?(i.delete(e.sessionId),h(e.sessionId),g({type:s.SESSION_REMOVED_TYPE,sessionId:e.sessionId})):e.kind===`channel`?(e.connectionId===a||e.connectionId===void 0&&i.has(e.sessionId))&&g({type:e.frameType,sessionId:e.sessionId,payload:e.payload}):(c.parseDoomNotificationEntry(e.frame)!==void 0||i.has(e.sessionId))&&g(s.sessionFrameEnvelope(e.sessionId,e.frame))}),u=V.onFrame(e=>{o.has(Me(e.sessionId,e.threadId))&&g(s.threadFrameEnvelope(e.sessionId,e.threadId,e.frame))}),d(`browser attached`)},onMessage(e){if(typeof e.data!=`string`)return;let n;try{n=JSON.parse(e.data)}catch{return}if(!t){if(r===void 0)return;let e=r.open(n);if(!e.ok)return;try{n=JSON.parse(new TextDecoder().decode(e.plaintext))}catch{return}}if(!I(n)||typeof n.sessionId!=`string`)return;let c=n.sessionId;if(typeof n.type==`string`&&B.channelTypes().includes(n.type)&&(i.has(c)||B.channelReceivesWithoutSubscription(c,n.type))){B.receiveChannel(c,n.type,n.payload,a);return}if(n.type===`subscribe`){let e=B.backlog(c);if(!e)return;i.add(c),f?.post(e);for(let e of B.channelFrames(c))f?.post(e);return}if(n.type===`unsubscribe`){i.delete(c);return}if(n.type===`history_request`){let e=B.history(c,{...typeof n.before==`string`?{before:n.before}:{},...typeof n.limit==`number`?{limit:n.limit}:{}});if(!e)return;f?.post(e);return}if(n.type===`subscribe_thread`||n.type===`unsubscribe_thread`){if(typeof n.threadId!=`string`)return;let e=n.threadId,t=Me(c,e);if(n.type===`unsubscribe_thread`){o.delete(t)&&V.unsubscribe(c,e);return}if(o.has(t)||B.backlog(c)===void 0)return;o.set(t,{sessionId:c,threadId:e}),f?.post(s.threadBacklog(c,e,V.subscribe(c,e)));return}if(n.type===`session_command`&&I(n.frame)){if(n.frame.type===`attach`)return;B.command(c,n.frame)}},onClose(){B.disconnectChannels(a),f&&H.delete(f),f=void 0,p?.(),p=void 0,l?.(),l=void 0,u?.(),u=void 0,i.clear(),h(),d(`browser detached`)}}})),U.get(m.PROTOCOL_SOCKET_ROUTE,W.upgradeWebSocket(e=>{let t=Y.listenerOf(e)===`local`,n=t?void 0:J.authorize((0,T.getCookie)(e,m.DEVICE_COOKIE,`host`)),r=n===void 0?void 0:J.channelFor(n,`protocol`),i,a;return{onOpen(e,o){if(!t&&r===void 0){o.close(1008,`sealed protocol channel required`);return}n!==void 0&&(a=J.trackSocket(n,(e,t)=>o.close(e,t))),i=(t?Ge:Ke).accept({send:e=>{if(t){o.send(e);return}if(r===void 0)return;let n=e instanceof Uint8Array?e:new Uint8Array(e),i=r.seal(n);if(!i.ok){o.close(1008,`sealed protocol channel failed`);return}o.send(new TextEncoder().encode(JSON.stringify(i.envelope)))},close:()=>o.close(),get readyState(){return o.readyState}})},async onMessage(e){let n=e.data;if(typeof n==`string`)return;let a=n instanceof Blob?new Uint8Array(await n.arrayBuffer()):(n instanceof ArrayBuffer,new Uint8Array(n));if(t){i?.onData(a);return}if(r===void 0)return;let o;try{o=JSON.parse(new TextDecoder().decode(a))}catch{return}let s=r.open(o);s.ok&&i?.onData(s.plaintext)},onClose(){a?.(),a=void 0,i?.onClose(),i=void 0},onError(e){i?.onError(e instanceof Error?e:Error(`The protocol socket failed`)),i=void 0}}})),U.get(`/manifest.webmanifest`,e=>{let t=F(y.default.join(G,`manifest.webmanifest`));return t===void 0?e.text(`The PWA manifest is unavailable.`,404):e.body(new Uint8Array(t),200,{"Cache-Control":`no-store`,"Content-Type":`application/manifest+json; charset=utf-8`})}),U.get(`/sw.js`,e=>{let t=F(y.default.join(G,`sw.js`));return t===void 0?e.text(`The trusted service worker is unavailable.`,404):e.body(new Uint8Array(t),200,{"Cache-Control":`no-cache`,"Content-Type":`text/javascript; charset=utf-8`,"Service-Worker-Allowed":`/`})}),U.get(`/pwa/*`,e=>{let t=new URL(e.req.url).pathname.slice(5),n=o.resolveAssetPath(G,`/${t}`),r=n===void 0?void 0:F(n);return n===void 0||r===void 0?e.text(`PWA asset not found.`,404):e.body(new Uint8Array(r),200,{"Cache-Control":`no-store`,"Content-Type":o.contentTypeFor(n),"X-Content-Type-Options":`nosniff`})}),U.get(`${A}*`,e=>{let t=new URL(e.req.url).pathname,n=/^\/api\/web-plugins\/([a-f0-9]{64})\/([1-9][0-9]*)\/(manifest|assets(\/.*))$/u.exec(t),r=n===null?void 0:Number(n[2]),i=n===null||!Number.isSafeInteger(r)?void 0:z.get(n[1]??``,r);if(n===null||i===void 0)return e.text(`That signed plugin composition is unavailable.`,404);if(n[3]===`manifest`)return e.json(i.signed,200,{"Cache-Control":`no-store`});let a=(0,w.assetFor)(i.signed.manifest,n[4]??``),s=a===void 0?void 0:o.resolveAssetPath(i.assetsDir,a.path),c=s===void 0?void 0:F(s);return a===void 0||s===void 0||c===void 0?e.text(`Plugin composition asset not found.`,404):e.body(new Uint8Array(c),200,{"Cache-Control":`no-store`,"Content-Length":String(c.byteLength),"Content-Type":a.contentType,"X-Content-Type-Options":`nosniff`})}),U.get(w.BUNDLE_MANIFEST_ROUTE,e=>{let t=K.current();return t===void 0?e.json({error:`No signed cockpit bundle is available.`},503):e.json(t.signed,200,{"Cache-Control":`no-store`})}),U.get(`/bundle-assets/*`,e=>{let t=new URL(e.req.url).pathname,n=/^\/bundle-assets\/([1-9][0-9]*)(\/.*)$/u.exec(t),r=n===null?void 0:Number(n[1]),i=K.current();if(n===null||!Number.isSafeInteger(r)||i===void 0||i.signed.manifest.revision!==r)return e.text(`That signed bundle revision is unavailable.`,404);let a=(0,w.assetFor)(i.signed.manifest,n[2]??``),s=a===void 0?void 0:o.resolveAssetPath(i.assetsDir,a.path),c=s===void 0?void 0:F(s);return a===void 0||s===void 0||c===void 0?e.text(`Bundle asset not found.`,404):e.body(new Uint8Array(c),200,{"Cache-Control":`no-store`,"Content-Length":String(c.byteLength),"Content-Type":a.contentType,"X-Content-Type-Options":`nosniff`})}),U.get(`/`,e=>e.redirect(m.PAIRING_PAGE_ROUTE)),U.get(`*`,e=>{let t=new URL(e.req.url).pathname,n=e.req.header(`accept`)?.includes(`text/html`)===!0;return!t.startsWith(`/api/`)&&n?e.redirect(`/`):e.text(`Not found.`,404)}),M.watch(()=>{for(let e of B.records())D(e.cwd)===j&&B.reloadChannels(e.id)}),new Promise((t,n)=>{let r=(0,ge.serve)({fetch:U.fetch,port:e.port,hostname:l},e=>{Re=e.port,W.injectWebSocket(r);let n=`http://${l}:${e.port}`;d(`cockpit on ${n}`);let i,a=async()=>{await J.close(),await new Promise(e=>{V.close(),M.close(),Z.close(),Ye.close(),Be(),q?.close(),B.close(),Q.close();for(let e of $)e.close();z.close();for(let e of W.wss.clients)e.terminate();W.wss.close(),r.closeAllConnections?.(),r.close(()=>e())}),await _.shutdownWebTelemetry(f)};t({url:n,port:e.port,close:()=>i??=a()})});r.once(`error`,e=>{M.close(),V.close(),Be(),q?.close(),B.close(),Q.close(),J.close();for(let e of $)e.close();z.close(),f.shutdown(),n(e)})})}exports.createSettingsRepositoryRegistry=Pe,exports.packagedVersion=ke,exports.serveWeb=z;
3
3
  //# sourceMappingURL=httpServer.cjs.map