κ-store: 48 shell blobs (aggregate fb89925ddfec)
Browse files- .gitattributes +3 -0
- b/007141010ac01ca81bb4bf74ad758709586b2899c25d7b8f8cf412abf1998c62 +1 -0
- b/00cfa4960b319ba1ba7291cddb20dea130a407ce8b42426218854f4f68f4dcd7 +3 -0
- b/29f6cbec51d5d103e676c312ab27151e2aa98e38f05532b3ea4adec6df0b36bb +1 -0
- b/310c5b0dfd9e59f3fce276da94235913365a0e8570c2ce5d9290213ca0f3067d +378 -0
- b/331a35dfd2b4774673ed92da26b0c78fecd336a70ecc8ff890256e835f7ccfd9 +130 -0
- b/3370b22070b56218a57df6406e2e4ba7cc171e942d4d1f2ae4297ea2c4c08d9c +3 -0
- b/438bd84d968d05dadd9f0ac1fd49a00c9d24f88a4ac45a9f46df236e7af2961e +1 -0
- b/5deebbd5a4fd2e15c9dc97480f2ab2eabdc1b092a8be69d0ce0eb6af503cf943 +0 -0
- b/6d4dc944c87c9a8ca7dfa0c65244c010722defe6e010b8a43a4782e2366aa82e +1 -0
- b/6e815d50e2ba13e8011d0b43218ef9ae5be3fe466eabe4bdfecf4bdb73942946 +208 -0
- b/716223384b22303f78bbd63c947ab539dc9a2fd796104d9a2192b063df89b00c +24 -0
- b/7247a61220c39a750bc27806475dd592f15e763f3b128c54e2c0580039aec673 +41 -0
- b/7d883ae6cf3e4495a3377e83d4cedef8f4141c15cf8166884e0efad2592f15e9 +262 -0
- b/86a3f68c5070d7a543e52768811268d22f2051a5033c11d5281ecd2a36c2dd44 +72 -0
- b/8c30aa5582dffe4548d27fa7863163eba689ed65126be472c003f5884fc8e534 +1870 -0
- b/8d34a1e9297fbaf472886031a90d3a06dfd4aa71c0621e4325e0112ef96fb07d +16 -0
- b/9d90b4ea8bccee8c239073c7ec7dd5158c6d11f1f668487570d17475d79e661e +3 -0
- b/a2276a0af47a3bc0c9c83d8d370bcc85c07e9c0f516b196ac158e575d5277b02 +1 -0
- b/a5b5d9c559a1d2812c33e9d7a4b1c24bb03059482503a23356e4c9c32d4d8b88 +0 -0
- b/b9f8a887de588c5193dc65ca9ce206a70c6df53595b848e0ce0299c2d88a5753 +0 -0
- b/c9a284fdd54f5472ac7d7781d9ac2d289f7dbacb9c710b0315bee75cd68ed99e +47 -0
- b/d1fdc38c3e96ab2c5c176a4e016516b27197ec0f8dde9467532e75703cacaa7e +0 -0
- b/d2e8a9ef211a9cf3d441297f50e5ef6949d8a7c43ebbe56705e75eb32e969937 +1 -0
- b/e0b2a13d7c3890a4ee3fc6120d2b42a3901ba068897aca8ce2080320f63446ee +0 -0
- b/e7b914a4e11aedb2e0239e7a84502a8241f2fff157d980741728810a1559ff41 +1 -0
- b/f011367faa1643ec7403b71b5e585c7a85ede5bd4a08e8ad5e95c31bcdf1161a +1 -0
- b/f4b80dc5a1ba9ad776b7b3793b5b9af2ed35592e45ce447eab9d571d5bc47693 +1 -0
.gitattributes
CHANGED
|
@@ -34,3 +34,6 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
b/f6caa9eeead2c99deeb2cdb70b7e25bbaa2cf5c60555f5b4030ecf6b6d9efa51 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
b/f6caa9eeead2c99deeb2cdb70b7e25bbaa2cf5c60555f5b4030ecf6b6d9efa51 filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
b/00cfa4960b319ba1ba7291cddb20dea130a407ce8b42426218854f4f68f4dcd7 filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
b/3370b22070b56218a57df6406e2e4ba7cc171e942d4d1f2ae4297ea2c4c08d9c filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
b/9d90b4ea8bccee8c239073c7ec7dd5158c6d11f1f668487570d17475d79e661e filter=lfs diff=lfs merge=lfs -text
|
b/007141010ac01ca81bb4bf74ad758709586b2899c25d7b8f8cf412abf1998c62
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import"./chunk-SQE76S5B.js";var I=1,k={standard:{name:"Standard",tag:"matte black",locked:!1},volt:{name:"Volt",tag:"electric streaks",locked:!1},fiber:{name:"Fiber",tag:"woven copper",locked:!1},sovereign:{name:"Sovereign",tag:"TEE holders only",locked:!0}},S=["v","kind","chain","address","label","edition","holder","created"];function g(e){return JSON.stringify(S.map(o=>e[o]??null))}async function b(e){let o=await crypto.subtle.digest("SHA-256",new TextEncoder().encode(e));return[...new Uint8Array(o)].map(t=>t.toString(16).padStart(2,"0")).join("")}async function w({chain:e="ethereum",address:o="",label:t="Card",edition:a="standard",holder:l=""}={}){let i={v:1,kind:"card",chain:e,address:o,label:t,edition:a,holder:l,created:Date.now(),frozen:!1};return i.kappa=await b(g(i)),i}async function R(e){return e&&e.kappa===await b(g(e))}function y(e){let o=parseInt(e.slice(0,8),16)>>>0,t=parseInt(e.slice(8,16),16)>>>0,a=parseInt(e.slice(16,24),16)>>>0,l=parseInt(e.slice(24,32),16)>>>0;return()=>{let i=(o+t>>>0)+l>>>0;return l=l+1>>>0,o=t^t>>>9,t=a+(a<<3)>>>0,a=(a<<21|a>>>11)>>>0,a=a+i>>>0,(i>>>0)/4294967296}}var s=856,n=540;function p(e,o,t){let a=e.createLinearGradient(0,0,s*.35,n);a.addColorStop(0,o),a.addColorStop(1,t),e.fillStyle=a,e.fillRect(0,0,s,n)}function d(e,o,t,a){e.save();for(let l=0;l<t;l++)e.fillStyle=`rgba(255,255,255,${o()*a})`,e.fillRect(o()*s,o()*n,1,1);e.restore()}function h(e,o){p(e,"#101114","#050506");let t=s*(.2+o()*.6),a=e.createLinearGradient(t-180,0,t+180,n);a.addColorStop(0,"rgba(255,255,255,0)"),a.addColorStop(.5,"rgba(255,255,255,0.045)"),a.addColorStop(1,"rgba(255,255,255,0)"),e.fillStyle=a,e.fillRect(0,0,s,n),d(e,o,900,.05)}function m(e,o){p(e,"#04070f","#010208"),e.save(),e.globalCompositeOperation="lighter";let t=205+o()*40;for(let a=0;a<42;a++){let l=n*o(),i=n*o(),f=s*o(),r=s*o(),u=t+(o()-.5)*50;e.strokeStyle=`hsla(${u}, 100%, ${55+o()*25}%, ${.03+o()*.1})`,e.lineWidth=.5+o()*3.2,e.beginPath(),e.moveTo(-40,l),e.bezierCurveTo(f,l+(o()-.5)*260,r,i+(o()-.5)*260,s+40,i),e.stroke()}e.restore(),d(e,o,500,.04)}function C(e,o){p(e,"#12080a","#070304"),e.save();let t=s*(.3+o()*.4);for(let a=-4;a<n+4;a+=2){let l=Math.exp(-Math.abs(a-n*.45)/(n*.5)),f=8+o()*30+l*22;e.strokeStyle=`hsla(${14+o()*26}, ${55+o()*35}%, ${f}%, ${.5+o()*.5})`,e.lineWidth=1+o()*1.6,e.beginPath();let r=(o()-.5)*22;e.moveTo(0,a),e.bezierCurveTo(t*.5,a+r,t,a-r,s,a+(o()-.5)*14),e.stroke()}e.globalCompositeOperation="lighter";for(let a=0;a<60;a++){let l=n*o();e.strokeStyle=`hsla(${20+o()*25}, 100%, ${60+o()*25}%, ${.1+o()*.22})`,e.lineWidth=.6+o(),e.beginPath();let i=s*o()*.7;e.moveTo(i,l),e.lineTo(i+s*(.15+o()*.5),l+(o()-.5)*10),e.stroke()}e.restore()}function v(e,o){p(e,"#0a0a12","#040408"),e.save(),e.globalCompositeOperation="lighter";let t=o()*Math.PI*2;for(let a=0;a<26;a++){let l=a/26,i=(t*57+l*320+o()*30)%360,f=n*(l+(o()-.5)*.06),r=e.createLinearGradient(0,f-60,s,f+60);r.addColorStop(0,`hsla(${i},90%,60%,0)`),r.addColorStop(.5,`hsla(${i},90%,60%,${.05+o()*.06})`),r.addColorStop(1,`hsla(${(i+60)%360},90%,60%,0)`),e.fillStyle=r,e.beginPath(),e.ellipse(s/2,f,s*(.55+o()*.25),46+o()*40,(o()-.5)*.22,0,Math.PI*2),e.fill()}e.restore(),d(e,o,700,.05)}var T={standard:h,volt:m,fiber:C,sovereign:v};function $(e,o){e.save(),e.fillStyle="rgba(255,255,255,0.94)",e.font="600 44px ui-sans-serif, system-ui, 'Segoe UI', Roboto, sans-serif",e.textBaseline="top",e.fillText("Holo",52,46),e.font="300 44px ui-sans-serif, system-ui, 'Segoe UI', Roboto, sans-serif",e.fillText("Money",158,46),e.font="700 22px ui-sans-serif, system-ui, sans-serif",e.fillStyle="rgba(255,255,255,0.62)";let a="VIRTUAL";e.fillText(a,s-52-e.measureText(a).width,56),e.textBaseline="alphabetic",e.fillStyle="rgba(255,255,255,0.88)",e.font="500 30px ui-sans-serif, system-ui, sans-serif",e.fillText(o.holder||"Operator",52,n-52-34),e.fillStyle="rgba(255,255,255,0.55)",e.font="400 24px ui-monospace, Consolas, monospace";let l=(o.address||o.kappa||"").slice(-4);e.fillText(`${o.label} \xB7\xB7${l}`,52,n-52);let i=n-52-26,f=s-52-34,r=30;e.globalAlpha=.9,e.fillStyle="rgba(255,255,255,0.22)",e.beginPath(),e.arc(f-20,i,r,0,Math.PI*2),e.fill(),e.fillStyle="rgba(255,255,255,0.34)",e.beginPath(),e.arc(f+20,i,r,0,Math.PI*2),e.fill(),e.globalAlpha=1,e.fillStyle="rgba(10,10,14,0.85)",e.font="800 30px ui-sans-serif, system-ui, sans-serif",e.fillText("H",f-11,i+11),e.restore()}function A(e,o){e.width=s,e.height=n;let t=e.getContext("2d"),a=y(o.kappa||"deadbeefdeadbeefdeadbeefdeadbeef");(T[o.edition]||h)(t,a);let l=t.createRadialGradient(s/2,n/2,n*.4,s/2,n/2,s*.75);return l.addColorStop(0,"rgba(0,0,0,0)"),l.addColorStop(1,"rgba(0,0,0,0.38)"),t.fillStyle=l,t.fillRect(0,0,s,n),$(t,o),e}export{I as CARD_VERSION,k as EDITIONS,n as FACE_H,s as FACE_W,w as mintCard,A as renderFace,b as sha256hex,R as verifyCard};
|
b/00cfa4960b319ba1ba7291cddb20dea130a407ce8b42426218854f4f68f4dcd7
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:00cfa4960b319ba1ba7291cddb20dea130a407ce8b42426218854f4f68f4dcd7
|
| 3 |
+
size 2926068
|
b/29f6cbec51d5d103e676c312ab27151e2aa98e38f05532b3ea4adec6df0b36bb
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{a as d,b as y,c as w,d as k}from"./chunk-67U7RCKQ.js";import"./chunk-SQE76S5B.js";function m(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function v(e,{claimSecret:s,signer:a,at:t}){let i={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:s,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:a.pub,at:t};return i.sig=await a.sign(m(i)),i.tid=await d(m(i)),i}async function S(e,{signer:s,at:a}){let t={type:"refund",kappa:e.kappa,expires:e.expires,by:s.pub,at:a};return t.sig=await s.sign(m(t)),t.tid=await d(m(t)),t}async function b(e,s){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await w(e.by,e.sig,m(e))?!1:e.type==="claim"?!(!e.preimage||await d(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&s>e.expires):!1}async function g(e,s=Date.now()){let a=[];for(let o of e)await b(o,s)&&a.push(o);let t=a.filter(o=>o.type==="claim");if(t.length){t.sort((r,n)=>r.at-n.at||(r.tid<n.tid?-1:r.tid>n.tid?1:0));let o=t[0];return{state:"claimed",winner:o.tid,by:o.by,amount:o.amount,asset:o.asset,fiat:o.fiat}}let i=a.filter(o=>o.type==="refund");return i.length?(i.sort((o,r)=>o.at-r.at||(o.tid<r.tid?-1:1)),{state:"refunded",winner:i[0].tid}):{state:"open"}}function D(e){return e&&e.transitions?Object.values(e.transitions):[]}async function j(e,s,a=Date.now()){e.transitions=e.transitions||{};for(let t of s||[])e.transitions[t.tid]||await b(t,a)&&(e.transitions[t.tid]=t);return e}var x=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),h=new Map;function p(){return{get:e=>h.get(e)||null,set:(e,s)=>h.set(e,s),has:e=>h.has(e)}}function C(e="holo-escrow"){if(typeof localStorage>"u")return p();let s=a=>e+":"+a;return{get:a=>{try{let t=localStorage.getItem(s(a));return t?JSON.parse(t):null}catch{return null}},set:(a,t)=>{try{localStorage.setItem(s(a),JSON.stringify(t))}catch{}},has:a=>{try{return localStorage.getItem(s(a))!=null}catch{return!1}}}}async function E(e,{store:s=null,wallet:a=null,chain:t="base",nowMs:i=null}={}){let o=s||p();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await k(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let r=i||Date.now();if(e.expires&&r>e.expires)return{ok:!1,error:"already expired"};let n=o.get(e.kappa);if(n&&n.funded)return{ok:!0,kappa:e.kappa,mode:n.mode,live:n.mode==="live",tx:n.tx,already:!0};let c=a&&typeof a.htlcFund=="function"?"live":"custodial",f=null;if(c==="live")try{f=await a.htlcFund({chain:t,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(l){return{ok:!1,error:String(l&&l.message||l)}}let u=n||x();return u.funded=!0,u.mode=c,u.tx=f,u.amount=e.amount,u.asset=e.asset,u.fiat=e.fiat||null,u.expires=e.expires,o.set(e.kappa,u),{ok:!0,kappa:e.kappa,mode:c,live:c==="live",tx:f}}async function F(e,{claimSecret:s=null,claimerPub:a=null,claimerSigner:t=null,store:i=null,nowMs:o=null}={}){let r=i||p(),n=o||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await k(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&n>e.expires)return{ok:!1,error:"this payment has expired"};let c=s||e.claimSecret;if(!c)return{ok:!1,error:"this link has no claim key"};if(await d(c)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let f=t||await y(),u=await v(e,{claimSecret:c,signer:f,at:n}),l=r.get(e.kappa)||x();l.funded||(l.offDevice=!0);let M=l.committed&&l.committed!==u.tid;return l.transitions[u.tid]=u,l.committed||(l.committed=u.tid),r.set(e.kappa,l),M?{ok:!1,error:"already claimed"}:{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:l.mode,live:l.mode==="live",offDevice:!!l.offDevice,tid:u.tid,claimerPub:a||f.pub,custodialWallet:"\u03BA:"+e.kappa.slice(0,16)}}async function J(e,{store:s=null,signer:a=null,nowMs:t=null}={}){let i=s||p(),o=t||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let r=i.get(e.kappa);if(!r)return{ok:!1,error:"no escrow to refund"};let n=await g(Object.values(r.transitions),o);if(n.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(n.state==="refunded")return{ok:!1,error:"already refunded"};if(!(r.expires&&o>r.expires))return{ok:!1,error:"not yet expired, can't refund"};let c=a||await y(),f=await S(e,{signer:c,at:o});return r.transitions[f.tid]=f,i.set(e.kappa,r),{ok:!0,amount:r.amount,asset:r.asset,mode:r.mode}}async function _(e,{store:s=null,nowMs:a=null}={}){let t=s||p(),i=a||Date.now(),o=typeof e=="string"?e:e&&e.kappa;if(!o)return{state:"none"};let r=t.get(o);if(!r)return{state:"none"};let n=await g(Object.values(r.transitions),i);return n.state==="open"?{state:r.funded?"open":"none",committed:r.committed||null,canonicalWinner:null,reconciled:!1}:{state:n.state,canonicalWinner:n.winner,committed:r.committed||null,reconciled:!!(r.committed&&n.state==="claimed"&&r.committed!==n.winner),by:n.by,amount:n.amount,asset:n.asset}}function N(e,{store:s=null}={}){let a=s||p(),t=typeof e=="string"?e:e&&e.kappa,i=a.get(t);return i?D(i):[]}async function L(e,s,{store:a=null,nowMs:t=null}={}){let i=a||p(),o=t||Date.now(),r=typeof e=="string"?e:e&&e.kappa,n=i.get(r)||x();return await j(n,s||[],o),i.set(r,n),_(r,{store:i,nowMs:o})}export{F as claimEscrow,N as exportPeer,E as fund,C as localStore,L as mergePeer,J as refund,_ as status};
|
b/310c5b0dfd9e59f3fce276da94235913365a0e8570c2ce5d9290213ca0f3067d
ADDED
|
@@ -0,0 +1,378 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// messenger-shadcn-ui.mjs — DROP-IN mount(el, model) rendering the Holo Messenger UX from STREAMING
|
| 2 |
+
// shadcn κ-components, faithful to the real app (network app-rail · Q sidebar · triage · rich rows ·
|
| 3 |
+
// Q chat pane · earth wallpaper · real SVG line icons), same contract as the chatscope bundle.
|
| 4 |
+
//
|
| 5 |
+
// ADDITIVE + SAFE: only takes over window.HoloMessengerUI.mount on opt-in (?ui=shadcn or
|
| 6 |
+
// localStorage["holo.ui.shadcn"]="1"). Default boot stays the untouched chatscope UI.
|
| 7 |
+
//
|
| 8 |
+
// PERF: memoized rows · composer owns its draft · windowed thread (≤WINDOW rows in DOM; 1M chat stays tiny).
|
| 9 |
+
// PASS 1 = faithful shell. Rich message content + action surface are Pass 2/3.
|
| 10 |
+
|
| 11 |
+
const WANT = (() => {
|
| 12 |
+
try { const u = new URL(location.href);
|
| 13 |
+
if (u.searchParams.get("ui") === "shadcn") return true;
|
| 14 |
+
if (u.searchParams.get("ui") === "chatscope") return false;
|
| 15 |
+
return localStorage.getItem("holo.ui.shadcn") === "1";
|
| 16 |
+
} catch { return false; }
|
| 17 |
+
})();
|
| 18 |
+
|
| 19 |
+
const WINDOW = 80;
|
| 20 |
+
const BRANDS = "/usr/share/brands/";
|
| 21 |
+
const WP = "/apps/holo-messenger/_vendor/wallpaper-default.jpg"; // the earth photo (default)
|
| 22 |
+
const NET_SLUG = { whatsapp: "whatsapp", telegram: "telegram", signal: "signal", imessage: "imessage", instagram: "instagram", messenger: "messenger", discord: "discord", slack: "slack", gmessages: "googlemessages", googlemessages: "googlemessages", x: "x", twitter: "x", linkedin: "linkedin", gmail: "gmail", email: "gmail", internal: null };
|
| 23 |
+
|
| 24 |
+
const CSS = `
|
| 25 |
+
.wa{height:100dvh;display:grid;grid-template-columns:64px minmax(300px,.46fr) minmax(0,1fr);background:#0b141a;color:#e9edef;
|
| 26 |
+
font:14.5px/1.5 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
|
| 27 |
+
.wa *{box-sizing:border-box}
|
| 28 |
+
.wa svg{display:block}
|
| 29 |
+
.rail{background:#111b21;border-right:1px solid #0a1015;display:flex;flex-direction:column;align-items:center;gap:7px;padding:12px 0}
|
| 30 |
+
.rb{position:relative;width:46px;height:46px;border-radius:15px;display:grid;place-items:center;cursor:pointer;color:#aebac1;transition:background .12s,color .12s;background:transparent;border:0}
|
| 31 |
+
.rb:hover{background:#202c33;color:#e9edef}.rb.on{background:#2a3942;color:#00a884}
|
| 32 |
+
.rb img{width:28px;height:28px;border-radius:8px;object-fit:cover}
|
| 33 |
+
.rb .dot{position:absolute;right:5px;bottom:5px;width:10px;height:10px;border-radius:50%;background:#00d95f;border:2px solid #111b21}
|
| 34 |
+
.rb .bdg{position:absolute;top:-3px;right:-3px;min-width:19px;height:19px;border-radius:10px;background:#00a884;color:#0b141a;font-size:11px;font-weight:700;display:grid;place-items:center;padding:0 4px;border:2px solid #111b21}
|
| 35 |
+
.rsep{width:28px;height:1px;background:#22303a;margin:3px 0}
|
| 36 |
+
.rgrow{flex:1 1 auto}
|
| 37 |
+
.rav{width:40px;height:40px;border-radius:50%;overflow:hidden;cursor:pointer;background:radial-gradient(circle at 35% 30%,#7cf0c8,#2b9e7a 60%,#0b3b2e);margin-top:2px}
|
| 38 |
+
.side{background:#111b21;border-right:1px solid #222d34;display:flex;flex-direction:column;min-width:0}
|
| 39 |
+
.stitle{display:flex;align-items:center;gap:6px;padding:15px 14px 10px}
|
| 40 |
+
.stitle .t{font-size:23px;font-weight:600;letter-spacing:-.4px;flex:1 1 auto}
|
| 41 |
+
.search{padding:4px 12px 8px;position:relative}
|
| 42 |
+
.search .si{position:absolute;left:26px;top:50%;transform:translateY(-50%);color:#8696a0;pointer-events:none;z-index:1;display:grid}
|
| 43 |
+
.filt{display:flex;align-items:center;gap:8px;padding:2px 14px 8px}
|
| 44 |
+
.chip{display:inline-flex;align-items:center;gap:6px;background:#202c33;color:#8696a0;border:0;border-radius:999px;padding:5px 14px;font-size:13px;cursor:pointer}
|
| 45 |
+
.chip.on{background:#0b3b2e;color:#00d95f}
|
| 46 |
+
.caret{background:#202c33;color:#8696a0;border:0;border-radius:999px;width:30px;height:28px;display:grid;place-items:center;cursor:pointer;font-size:11px}
|
| 47 |
+
.triage{margin:2px 12px 8px;display:flex;align-items:center;gap:9px;background:linear-gradient(90deg,#0e2a24,#122a1e);color:#7cf0c8;border:1px solid #123c30;border-radius:14px;padding:9px 14px;font-size:13.5px;cursor:pointer}
|
| 48 |
+
.triage .sk{color:#00d95f;display:grid;flex:0 0 auto}
|
| 49 |
+
.chats{flex:1 1 auto;overflow-y:auto;overflow-x:hidden}
|
| 50 |
+
.chats::-webkit-scrollbar{width:6px}.chats::-webkit-scrollbar-thumb{background:#374248;border-radius:6px}
|
| 51 |
+
.row{display:flex;gap:13px;align-items:center;padding:9px 14px;cursor:pointer;position:relative}
|
| 52 |
+
.row:after{content:"";position:absolute;left:78px;right:0;bottom:0;height:1px;background:#1c262d}
|
| 53 |
+
.row:hover{background:#202c33}.row.on{background:#2a3942}.row.on:after{background:transparent}
|
| 54 |
+
.av{position:relative;flex:0 0 auto;border-radius:50%;overflow:hidden}
|
| 55 |
+
.av img{width:100%;height:100%;object-fit:cover;display:block}
|
| 56 |
+
.row .b{min-width:0;flex:1 1 auto}
|
| 57 |
+
.row .n{color:#e9edef;font-size:16px;font-weight:400;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:6px}
|
| 58 |
+
.row .p{color:#8696a0;font-size:13.5px;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:flex;align-items:center;gap:3px}
|
| 59 |
+
.row .rc{color:#53bdeb}
|
| 60 |
+
.row .m{margin-left:auto;text-align:right;display:flex;flex-direction:column;align-items:flex-end;gap:6px;flex:0 0 auto}
|
| 61 |
+
.row .t{color:#8696a0;font-size:12px}.row.unread .t{color:#00d95f;font-weight:500}
|
| 62 |
+
.row .pin{color:#f15c6d;font-size:13px;transform:rotate(45deg)}
|
| 63 |
+
.netdot{width:16px;height:16px;border-radius:4px;object-fit:cover;flex:0 0 auto}
|
| 64 |
+
.main{position:relative;display:flex;flex-direction:column;min-width:0;background:#0b141a}
|
| 65 |
+
.wp{position:absolute;inset:0;background:#0b141a center/cover no-repeat;z-index:0}
|
| 66 |
+
.wp:after{content:"";position:absolute;inset:0;background:linear-gradient(rgba(11,20,26,.35),rgba(11,20,26,.5))}
|
| 67 |
+
.mh,.thread,.composer,.e2ewrap{position:relative;z-index:1}
|
| 68 |
+
.mh{height:60px;flex:0 0 auto;background:#202c33;display:flex;align-items:center;gap:14px;padding:0 16px}
|
| 69 |
+
.mh .who .n{font-size:16px;color:#e9edef;font-weight:500}
|
| 70 |
+
.mh .who .s{font-size:13px;color:#00a884;margin-top:1px}
|
| 71 |
+
.mh .sp{margin-left:auto}.mh .icons{display:flex;gap:2px}
|
| 72 |
+
.ico{width:40px;height:40px;border-radius:50%;display:grid;place-items:center;color:#aebac1;cursor:pointer;transition:background .12s;background:transparent;border:0}
|
| 73 |
+
.ico:hover{background:#ffffff14;color:#e9edef}
|
| 74 |
+
.ico.sk{color:#00d95f}
|
| 75 |
+
.e2ewrap{display:flex;justify-content:center;padding:14px 0 2px}
|
| 76 |
+
.e2e{background:#182229;color:#ffd279;font-size:12.5px;padding:6px 14px;border-radius:8px;box-shadow:0 1px .5px #0003}
|
| 77 |
+
.thread{flex:1 1 auto;overflow-y:auto;overflow-x:hidden;padding:10px 8% 14px;display:flex;flex-direction:column;gap:3px}
|
| 78 |
+
.thread::-webkit-scrollbar{width:6px}.thread::-webkit-scrollbar-thumb{background:#374248cc;border-radius:6px}
|
| 79 |
+
.more{align-self:center;color:#8696a0;font-size:12px;padding:8px;opacity:.85}
|
| 80 |
+
.day{align-self:center;background:#182229;color:#8696a0;font-size:12.5px;padding:5px 12px;border-radius:8px;margin:8px 0 6px;box-shadow:0 1px .5px #0003}
|
| 81 |
+
.bub{max-width:65%;padding:6px 9px 8px 10px;border-radius:8px;font-size:14.2px;line-height:1.42;box-shadow:0 1px .5px #0003;word-wrap:break-word;white-space:pre-wrap}
|
| 82 |
+
.in{align-self:flex-start;background:#202c33;border-top-left-radius:0}
|
| 83 |
+
.out{align-self:flex-end;background:#005c4b;border-top-right-radius:0}
|
| 84 |
+
.grp{margin-top:8px}
|
| 85 |
+
.bub .tt{font-size:11px;color:#ffffff8a;float:right;margin:6px 0 -3px 12px;position:relative;top:4px}
|
| 86 |
+
.bub .tt .rc{color:#53bdeb}
|
| 87 |
+
.empty{margin:auto;color:#8696a0;text-align:center;background:#182229;padding:8px 16px;border-radius:8px}
|
| 88 |
+
.composer{flex:0 0 auto;background:#202c33;display:flex;align-items:center;gap:6px;padding:8px 14px}
|
| 89 |
+
.composer .grow{flex:1 1 auto}
|
| 90 |
+
.send{width:44px;height:44px;border-radius:50%;background:#00a884;color:#0b141a;border:0;display:grid;place-items:center;cursor:pointer;flex:0 0 auto}
|
| 91 |
+
/* media */
|
| 92 |
+
.m-img{display:block;max-width:320px;max-height:340px;border-radius:6px;cursor:pointer;object-fit:cover}
|
| 93 |
+
.m-vid{position:relative;max-width:320px;border-radius:6px;overflow:hidden;cursor:pointer}
|
| 94 |
+
.m-vid img{display:block;width:100%;border-radius:6px}
|
| 95 |
+
.m-play{position:absolute;inset:0;display:grid;place-items:center}
|
| 96 |
+
.m-play span{width:48px;height:48px;border-radius:50%;background:#0009;color:#fff;display:grid;place-items:center;font-size:20px}
|
| 97 |
+
.m-file{display:flex;align-items:center;gap:10px;background:#ffffff0f;border-radius:8px;padding:9px 11px;min-width:200px;cursor:pointer}
|
| 98 |
+
.m-file .fi{width:38px;height:38px;border-radius:8px;background:#00a88433;color:#7cf0c8;display:grid;place-items:center;font-size:16px;flex:0 0 auto}
|
| 99 |
+
.m-file .fn{font-size:13.5px;color:#e9edef;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
| 100 |
+
.m-file .fs{font-size:12px;color:#8696a0;margin-top:2px}
|
| 101 |
+
.m-aud{display:flex;align-items:center;gap:10px;min-width:220px;padding:2px 0}
|
| 102 |
+
.m-aud audio{height:34px}
|
| 103 |
+
.lb{position:fixed;inset:0;background:#000c;z-index:60;display:grid;place-items:center;cursor:zoom-out}
|
| 104 |
+
.lb img,.lb video{max-width:90vw;max-height:90vh;border-radius:8px}
|
| 105 |
+
.lb .x{position:absolute;top:18px;right:22px;color:#fff;font-size:28px;cursor:pointer;background:0;border:0}
|
| 106 |
+
/* attach tray + pay sheet */
|
| 107 |
+
.tray{position:absolute;bottom:66px;left:14px;background:#233138;border-radius:12px;box-shadow:0 10px 34px #0009;padding:6px;z-index:6;min-width:236px}
|
| 108 |
+
.tray button{display:flex;align-items:center;gap:11px;width:100%;background:0;border:0;color:#e9edef;font-size:14.5px;padding:10px 12px;border-radius:8px;cursor:pointer;text-align:left}
|
| 109 |
+
.tray button:hover{background:#2a3942}.tray button:disabled{opacity:.5}
|
| 110 |
+
.tray .sep{height:1px;background:#2a3942;margin:4px 6px}
|
| 111 |
+
.paywrap{position:fixed;inset:0;background:#000a;z-index:60;display:grid;place-items:center}
|
| 112 |
+
.pay{background:#111b21;border-radius:14px;padding:20px;width:346px;max-width:90vw;box-shadow:0 20px 60px #000a}
|
| 113 |
+
.pay h3{margin:0 0 4px;font-size:17px}.pay .psub{color:#8696a0;font-size:13px;margin-bottom:14px}
|
| 114 |
+
.pay .seg{display:flex;background:#202c33;border-radius:10px;padding:3px;margin-bottom:14px}
|
| 115 |
+
.pay .seg button{flex:1;background:0;border:0;color:#8696a0;padding:8px;border-radius:8px;cursor:pointer;font-size:14px}
|
| 116 |
+
.pay .seg button.on{background:#00a884;color:#0b141a;font-weight:600}
|
| 117 |
+
.pay .amt{width:100%;background:#202c33;border:0;border-radius:10px;color:#e9edef;font-size:26px;text-align:center;padding:12px;margin-bottom:10px;outline:0}
|
| 118 |
+
.pay .chips{display:flex;gap:8px;margin-bottom:12px}
|
| 119 |
+
.pay .chips button{flex:1;background:#202c33;border:0;color:#e9edef;border-radius:8px;padding:9px;cursor:pointer}
|
| 120 |
+
.pay .chips button.on{background:#0b3b2e;color:#00d95f}
|
| 121 |
+
.pay .memo{width:100%;background:#202c33;border:0;border-radius:10px;color:#e9edef;padding:10px 12px;margin-bottom:12px;font-size:14px;outline:0}
|
| 122 |
+
.pay .err{color:#f15c6d;font-size:13px;margin-bottom:8px;text-align:center}
|
| 123 |
+
.pay .go{width:100%;background:#00a884;color:#0b141a;border:0;border-radius:10px;padding:12px;font-weight:600;cursor:pointer;font-size:15px}
|
| 124 |
+
.pay .go:disabled{opacity:.5;cursor:default}
|
| 125 |
+
.pay .cancel{width:100%;background:0;color:#8696a0;border:0;padding:10px;margin-top:6px;cursor:pointer}
|
| 126 |
+
`;
|
| 127 |
+
|
| 128 |
+
async function ensureImportmap() {
|
| 129 |
+
if (document.querySelector('script[type="importmap"][data-holo-ui]')) return;
|
| 130 |
+
const map = await (await fetch("/apps/ui/vendor/importmap.json")).json();
|
| 131 |
+
const imports = {}; for (const [k, v] of Object.entries(map.imports || map)) imports[k] = typeof v === "string" ? v.replace(/^\.\//, "/apps/ui/") : v;
|
| 132 |
+
const s = document.createElement("script"); s.type = "importmap"; s.setAttribute("data-holo-ui", "1"); s.textContent = JSON.stringify({ imports }); document.head.appendChild(s);
|
| 133 |
+
}
|
| 134 |
+
let _deps = null;
|
| 135 |
+
async function deps() {
|
| 136 |
+
if (_deps) return _deps;
|
| 137 |
+
await ensureImportmap();
|
| 138 |
+
const React = await import("react");
|
| 139 |
+
const { createRoot } = await import("react-dom/client");
|
| 140 |
+
const reg = await (await fetch("/apps/ui/registry/index.json")).json();
|
| 141 |
+
const K = Object.fromEntries(reg.components.map((c) => [c.name, c.holo]));
|
| 142 |
+
const [button, badge, avatar, input] = await Promise.all(["button", "badge", "avatar", "input"].map((n) => import(K[n])));
|
| 143 |
+
return (_deps = { React, createRoot, C: { button, badge, avatar, input } });
|
| 144 |
+
}
|
| 145 |
+
if (!document.getElementById("wa-shadcn-css")) { const st = document.createElement("style"); st.id = "wa-shadcn-css"; st.textContent = CSS; document.head.appendChild(st); }
|
| 146 |
+
|
| 147 |
+
const initials = (s) => (String(s || "?").trim().split(/\s+/).map((w) => w[0]).join("").slice(0, 2) || "?").toUpperCase();
|
| 148 |
+
const hue = (s) => { let h = 0; s = String(s || ""); for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) >>> 0; return `hsl(${h % 360} 40% 46%)`; };
|
| 149 |
+
const isOut = (m) => !!(m && (m.mine || m.out || m.fromMe || m.dir === "out" || m.from === "me" || m.self));
|
| 150 |
+
const threadOf = (model, id) => { try { if (id && model.thread) return model.thread(id) || []; } catch {} return (model.threads && model.threads[id]) || []; };
|
| 151 |
+
const rcMark = (m) => m.status === "read" || m.status === "delivered" ? "✓✓" : m.status === "sent" ? "✓" : "";
|
| 152 |
+
const slugOf = (net) => { const k = String(net || "").toLowerCase(); return NET_SLUG[k] === undefined ? k : NET_SLUG[k]; };
|
| 153 |
+
const mimeKind = (mime, kind) => { const k = String(kind || "").toLowerCase(); if (["image", "video", "audio", "voice"].includes(k)) return k === "voice" ? "audio" : k; const t = String(mime || "").split("/")[0]; if (t === "image" || t === "video" || t === "audio") return t; return "file"; };
|
| 154 |
+
const humanSize = (n) => { n = +n || 0; if (!n) return ""; const u = ["B", "KB", "MB", "GB"]; let i = 0; while (n >= 1024 && i < u.length - 1) { n /= 1024; i++; } return (n < 10 && i ? n.toFixed(1) : Math.round(n)) + " " + u[i]; };
|
| 155 |
+
const fileGlyph = (mime, name) => { const m = String(mime || ""), e = String(name || "").split(".").pop().toLowerCase(); if (m.includes("pdf") || e === "pdf") return "📕"; if (["zip", "rar", "7z"].includes(e)) return "🗜"; if (["doc", "docx"].includes(e)) return "📘"; if (["xls", "xlsx", "csv"].includes(e)) return "📗"; return "📄"; };
|
| 156 |
+
|
| 157 |
+
function buildApp({ React, C }) {
|
| 158 |
+
const { createElement: h, memo, useState, useRef, useEffect, useCallback, useLayoutEffect } = React;
|
| 159 |
+
const Avatar = C.avatar.Avatar, AvatarImage = C.avatar.AvatarImage, AvatarFallback = C.avatar.AvatarFallback;
|
| 160 |
+
const Button = C.button.Button, Input = C.input.Input, Badge = C.badge.Badge;
|
| 161 |
+
|
| 162 |
+
// exact SVG line icons (from the real app's IC set)
|
| 163 |
+
const svg = (kids, s = 22, w = 1.8) => h("svg", { viewBox: "0 0 24 24", width: s, height: s, fill: "none", stroke: "currentColor", strokeWidth: w, strokeLinecap: "round", strokeLinejoin: "round" }, ...kids);
|
| 164 |
+
const P = (d, e) => h("path", { d, ...e });
|
| 165 |
+
const CIR = (cx, cy, r, e) => h("circle", { cx, cy, r, ...e });
|
| 166 |
+
const RC = (x, y, width, height, rx, e) => h("rect", { x, y, width, height, rx, ...e });
|
| 167 |
+
const fillC = { fill: "currentColor", stroke: "none" };
|
| 168 |
+
const IC = {
|
| 169 |
+
chats: () => svg([P("M20 11.4A7.6 7.6 0 0 1 8.9 18.2L4.5 19.4l1.2-4.2A7.6 7.6 0 1 1 20 11.4Z")]),
|
| 170 |
+
search: (s) => svg([CIR(11, 11, 7), P("m20 20-3.6-3.6")], s || 19),
|
| 171 |
+
phone: () => svg([P("M6.6 4H4.5A1.5 1.5 0 0 0 3 5.6 16 16 0 0 0 18.4 21a1.5 1.5 0 0 0 1.6-1.5v-2.1a1.5 1.5 0 0 0-1.2-1.45l-2.2-.45a1.5 1.5 0 0 0-1.5.6l-.5.7a12 12 0 0 1-5.1-5.1l.7-.5a1.5 1.5 0 0 0 .6-1.5l-.45-2.2A1.5 1.5 0 0 0 6.6 4Z")]),
|
| 172 |
+
video: () => svg([RC(3, 6.5, 12, 11, 2.5), P("m15 10.5 5.5-3v9l-5.5-3Z")]),
|
| 173 |
+
menu: () => svg([CIR(12, 5, 1.35, fillC), CIR(12, 12, 1.35, fillC), CIR(12, 19, 1.35, fillC)]),
|
| 174 |
+
newchat: () => svg([P("M13.5 4H6a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-7.5"), P("M18.4 3.6a2 2 0 0 1 2.8 2.8L13 14.6l-3.4.9.9-3.4 7.9-8.5Z")]),
|
| 175 |
+
emoji: () => svg([CIR(12, 12, 8.5), P("M8.8 14.2a4.2 4.2 0 0 0 6.4 0"), CIR(9, 10, 1, fillC), CIR(15, 10, 1, fillC)]),
|
| 176 |
+
plus: () => svg([P("M12 5.5v13M5.5 12h13")]),
|
| 177 |
+
mic: () => svg([RC(9, 3, 6, 11, 3), P("M5.5 11a6.5 6.5 0 0 0 13 0M12 17.5V21M8.5 21h7")]),
|
| 178 |
+
send: () => svg([P("M4.5 12 20.5 4.5 13 20.5l-2.8-6.4L4.5 12Z")]),
|
| 179 |
+
spark: (s) => svg([P("M12 3.2l1.7 4.6 4.6 1.7-4.6 1.7L12 15.8l-1.7-4.6L5.7 9.5l4.6-1.7L12 3.2Z"), P("M5.5 15.5l.8 2 2 .8-2 .8-.8 2-.8-2-2-.8 2-.8.8-2Z")], s || 20),
|
| 180 |
+
sun: () => svg([CIR(12, 12, 4), P("M12 2v2M12 20v2M4 12H2M22 12h-2M5.6 5.6 4.2 4.2M19.8 19.8l-1.4-1.4M18.4 5.6l1.4-1.4M4.2 19.8l1.4-1.4")]),
|
| 181 |
+
plusThin: () => svg([P("M12 5.5v13M5.5 12h13")], 26, 1.6),
|
| 182 |
+
};
|
| 183 |
+
|
| 184 |
+
const Av = (name, size, bg, src) => h("div", { className: "av", style: { height: size + "px", width: size + "px" } },
|
| 185 |
+
h(Avatar, { style: { height: "100%", width: "100%", background: bg || hue(name), display: "grid", placeItems: "center" } },
|
| 186 |
+
src ? h(AvatarImage, { src, alt: "", style: { height: "100%", width: "100%", objectFit: "cover" } }) : null,
|
| 187 |
+
h(AvatarFallback, { style: { background: bg || hue(name), color: "#fff", fontSize: (size * 0.36) + "px", fontWeight: 500, height: "100%", width: "100%", display: "grid", placeItems: "center" } }, initials(name))));
|
| 188 |
+
const Ico = (icon, onClick, title, cls) => h("button", { className: "ico" + (cls ? " " + cls : ""), onClick, title }, icon);
|
| 189 |
+
const netLogo = (net) => { const s = slugOf(net); return s ? h("img", { className: "netdot", src: BRANDS + s + ".svg", alt: "", onError: (e) => { e.currentTarget.style.display = "none"; } }) : null; };
|
| 190 |
+
|
| 191 |
+
// rich media in a bubble — image/video/audio/file, with lazy bridge-media resolution
|
| 192 |
+
function MediaView({ media, model, onMedia }) {
|
| 193 |
+
const [url, setUrl] = useState(media.url || media.thumb || "");
|
| 194 |
+
const bucket = mimeKind(media.mime, media.kind);
|
| 195 |
+
useEffect(() => { let alive = true;
|
| 196 |
+
if (!media.url && (media.lazy || media.pending || media.ref) && model.resolveBridgeMedia) {
|
| 197 |
+
Promise.resolve(model.resolveBridgeMedia(media)).then((u) => { if (alive && u) setUrl(typeof u === "string" ? u : (u.url || u.src || "")); }).catch(() => {});
|
| 198 |
+
} return () => { alive = false; }; }, []);
|
| 199 |
+
if (bucket === "image") return url ? h("img", { className: "m-img", src: url, alt: "", loading: "lazy", onClick: () => onMedia({ kind: "image", url }) }) : h("div", { className: "m-file" }, "🖼 Photo");
|
| 200 |
+
if (bucket === "video") return h("div", { className: "m-vid", onClick: () => onMedia({ kind: "video", url }) }, h("img", { src: media.thumb || url, alt: "" }), h("div", { className: "m-play" }, h("span", {}, "▶")));
|
| 201 |
+
if (bucket === "audio") return h("div", { className: "m-aud" }, url ? h("audio", { controls: true, src: url }) : h("span", {}, "🎤 Voice message"));
|
| 202 |
+
return h("div", { className: "m-file", onClick: () => url && window.open(url, "_blank") }, h("div", { className: "fi" }, fileGlyph(media.mime, media.filename)), h("div", { style: { minWidth: 0 } }, h("div", { className: "fn" }, media.filename || "File"), media.size ? h("div", { className: "fs" }, humanSize(media.size)) : null));
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
const Rail = memo(function Rail({ nets, active, totalUnread, onPick }) {
|
| 206 |
+
return h("nav", { className: "rail" },
|
| 207 |
+
h("button", { className: "rb" + (active === "All" ? " on" : ""), title: "All chats", onClick: () => onPick("All") }, IC.chats(), totalUnread ? h("span", { className: "bdg" }, totalUnread > 99 ? "99+" : totalUnread) : null),
|
| 208 |
+
nets.length ? h("div", { className: "rsep" }) : null,
|
| 209 |
+
nets.map((n) => h("button", { key: n.id, className: "rb" + (active === n.id ? " on" : ""), title: n.label, onClick: () => onPick(n.id) },
|
| 210 |
+
(() => { const s = slugOf(n.id); return s ? h("img", { src: BRANDS + s + ".svg", alt: n.label, onError: (e) => { const t = document.createTextNode((n.label || "?")[0].toUpperCase()); try { e.currentTarget.replaceWith(t); } catch {} } }) : (n.label || "?")[0].toUpperCase(); })(),
|
| 211 |
+
h("span", { className: "dot" }), n.unread ? h("span", { className: "bdg" }, n.unread > 99 ? "99+" : n.unread) : null)),
|
| 212 |
+
h("button", { className: "rb", title: "Add network", onClick: () => onPick("+") }, IC.plusThin()),
|
| 213 |
+
h("div", { className: "rgrow" }),
|
| 214 |
+
h("div", { className: "rav" }));
|
| 215 |
+
});
|
| 216 |
+
|
| 217 |
+
const Row = memo(function Row({ c, on, preview, receipt, onSelect }) {
|
| 218 |
+
return h("div", { className: "row" + (on ? " on" : "") + (c.unread ? " unread" : ""), onClick: () => onSelect(c.id) },
|
| 219 |
+
Av(c.name, 49, c.isQ ? "#12b886" : hue(c.name), c.avatar),
|
| 220 |
+
h("div", { className: "b" },
|
| 221 |
+
h("div", { className: "n" }, c.name || c.id || "Chat", c.network ? netLogo(c.network) : null),
|
| 222 |
+
h("div", { className: "p" }, receipt ? h("span", { className: "rc" }, receipt + " ") : null, preview)),
|
| 223 |
+
h("div", { className: "m" },
|
| 224 |
+
h("div", { className: "t" }, c.time || c.ts || ""),
|
| 225 |
+
c.pinned ? h("span", { className: "pin" }, "📌") : null,
|
| 226 |
+
c.unread ? h(Badge, { style: { background: "#00a884", color: "#0b141a", borderRadius: "999px", height: "20px", minWidth: "20px", justifyContent: "center", padding: "0 6px", fontSize: "12px", fontWeight: 600 } }, String(c.unread)) : null));
|
| 227 |
+
});
|
| 228 |
+
|
| 229 |
+
function Thread({ model, activeId, onMedia }) {
|
| 230 |
+
const all = threadOf(model, activeId);
|
| 231 |
+
const [limit, setLimit] = useState(WINDOW);
|
| 232 |
+
const ref = useRef(null), prevId = useRef(activeId), anchorH = useRef(0), pin = useRef(true);
|
| 233 |
+
const toBottom = (el) => { el.scrollTop = el.scrollHeight; };
|
| 234 |
+
useEffect(() => { setLimit(WINDOW); }, [activeId]);
|
| 235 |
+
// Runs after every render (no deps): a fresh chat, or new messages arriving, both flow through here.
|
| 236 |
+
// While "pinned" we stay glued to the newest message — exactly like WhatsApp.
|
| 237 |
+
useLayoutEffect(() => { const el = ref.current; if (!el) return;
|
| 238 |
+
if (prevId.current !== activeId) { prevId.current = activeId; pin.current = true; anchorH.current = 0; toBottom(el); return; }
|
| 239 |
+
if (anchorH.current) { el.scrollTop = el.scrollHeight - anchorH.current; anchorH.current = 0; return; }
|
| 240 |
+
if (pin.current) toBottom(el); });
|
| 241 |
+
// Media resolves async (placeholder first, <img> swapped in later) and reflows the thread AFTER layout.
|
| 242 |
+
// A ResizeObserver on the scroll container won't catch it (its own box size never changes), and per-image
|
| 243 |
+
// listeners miss images added later — so listen for `load` in the CAPTURE phase on the container, which
|
| 244 |
+
// fires for ANY descendant image whenever it appears/loads. Keeps us glued to the newest message.
|
| 245 |
+
useEffect(() => { const el = ref.current; if (!el) return; pin.current = true; toBottom(el);
|
| 246 |
+
requestAnimationFrame(() => { if (pin.current && ref.current) toBottom(ref.current); });
|
| 247 |
+
const stick = () => { if (pin.current && ref.current) toBottom(ref.current); };
|
| 248 |
+
el.addEventListener("load", stick, true); el.addEventListener("error", stick, true);
|
| 249 |
+
return () => { el.removeEventListener("load", stick, true); el.removeEventListener("error", stick, true); };
|
| 250 |
+
}, [activeId]);
|
| 251 |
+
// Detach when the user scrolls up; re-attach ("jump to latest") once they're back near the bottom.
|
| 252 |
+
const onScroll = useCallback((e) => { const el = e.currentTarget;
|
| 253 |
+
pin.current = el.scrollHeight - el.scrollTop - el.clientHeight < 80;
|
| 254 |
+
if (el.scrollTop < 120 && limit < all.length) { anchorH.current = el.scrollHeight; setLimit((l) => Math.min(all.length, l + WINDOW)); } }, [limit, all.length]);
|
| 255 |
+
const start = Math.max(0, all.length - limit), shown = all.slice(start);
|
| 256 |
+
if (!all.length) return h("div", { className: "thread", ref }, h("div", { className: "empty" }, "No messages yet. Say hello 👋"));
|
| 257 |
+
const rows = [h("div", { className: "e2ewrap", key: "e2e" }, h("div", { className: "e2e" }, "🔒 Messages are end-to-end encrypted")), h("div", { className: "day", key: "day" }, "Today")];
|
| 258 |
+
if (start > 0) rows.push(h("div", { className: "more", key: "more" }, `↑ ${start.toLocaleString()} earlier — scroll up`));
|
| 259 |
+
let lastOut = null;
|
| 260 |
+
shown.forEach((m, i) => { const out = isOut(m); const hasMedia = m.media && (m.media.url || m.media.thumb || m.media.ref || m.media.lazy || m.media.kind);
|
| 261 |
+
rows.push(h("div", { key: start + i, className: "bub " + (out ? "out" : "in") + (out !== lastOut ? " grp" : ""), style: hasMedia ? { maxWidth: "min(360px,72%)" } : null },
|
| 262 |
+
hasMedia ? h(MediaView, { media: m.media, model, onMedia }) : null,
|
| 263 |
+
m.text ? h("div", { style: hasMedia ? { marginTop: "5px" } : null }, m.text) : null,
|
| 264 |
+
h("span", { className: "tt" }, (m.time || m.ts || ""), out ? h("span", { className: "rc" }, " " + rcMark(m)) : null)));
|
| 265 |
+
lastOut = out; });
|
| 266 |
+
return h("div", { className: "thread", ref, onScroll }, rows);
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
const Composer = memo(function Composer({ activeId, onSend, onPlus }) {
|
| 270 |
+
const [draft, setDraft] = useState("");
|
| 271 |
+
const send = () => { const t = draft.trim(); if (!t) return; onSend(activeId, t); setDraft(""); };
|
| 272 |
+
return h("div", { className: "composer" },
|
| 273 |
+
Ico(IC.plus(), onPlus, "Attach"),
|
| 274 |
+
h("div", { className: "grow" }, h(Input, { placeholder: "Type a message", value: draft, autoFocus: true,
|
| 275 |
+
onChange: (e) => setDraft(e.target.value), onKeyDown: (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); send(); } },
|
| 276 |
+
style: { background: "#2a3942", border: "0", borderRadius: "10px", height: "44px", color: "#e9edef", paddingLeft: "16px" } })),
|
| 277 |
+
h("button", { className: "send", onClick: send, "aria-label": "Send" }, draft.trim() ? IC.send() : IC.mic()));
|
| 278 |
+
});
|
| 279 |
+
|
| 280 |
+
const TrayBtn = (glyph, label, onClick, disabled) => h("button", { onClick, disabled }, h("span", { style: { fontSize: "17px" } }, glyph), label);
|
| 281 |
+
|
| 282 |
+
function PaySheet({ model, activeId, conv, sheet, setSheet }) {
|
| 283 |
+
const set = (patch) => setSheet((s) => ({ ...s, ...patch, err: null }));
|
| 284 |
+
const amt = parseFloat(sheet.amount);
|
| 285 |
+
const go = async () => { set({ busy: true }); let r;
|
| 286 |
+
try { r = await model.holoPay(sheet.genesis || activeId, { kind: sheet.kind, amount: amt, memo: (sheet.memo || "").trim() }); } catch (e) { r = { ok: false, error: String(e && e.message || e) }; }
|
| 287 |
+
if (r && r.ok) setSheet(null); else setSheet((s) => ({ ...s, busy: false, err: (r && r.error) || "Couldn't create the payment" })); };
|
| 288 |
+
return h("div", { className: "paywrap", onClick: () => setSheet(null) },
|
| 289 |
+
h("div", { className: "pay", onClick: (e) => e.stopPropagation() },
|
| 290 |
+
h("h3", {}, sheet.kind === "send" ? "Send money" : "Request money"), h("div", { className: "psub" }, (sheet.kind === "send" ? "To " : "From ") + (conv.name || "this chat")),
|
| 291 |
+
h("div", { className: "seg" }, h("button", { className: sheet.kind === "send" ? "on" : "", onClick: () => set({ kind: "send" }) }, "Send"), h("button", { className: sheet.kind === "request" ? "on" : "", onClick: () => set({ kind: "request" }) }, "Request")),
|
| 292 |
+
h("input", { className: "amt", inputMode: "decimal", placeholder: "$0", value: sheet.amount, onChange: (e) => set({ amount: e.target.value.replace(/[^0-9.]/g, "") }), autoFocus: true }),
|
| 293 |
+
h("div", { className: "chips" }, [10, 20, 50, 100].map((a) => h("button", { key: a, className: amt === a ? "on" : "", onClick: () => set({ amount: String(a) }) }, "$" + a))),
|
| 294 |
+
h("input", { className: "memo", placeholder: "What's it for? (optional)", maxLength: 120, value: sheet.memo || "", onChange: (e) => set({ memo: e.target.value }) }),
|
| 295 |
+
sheet.err ? h("div", { className: "err" }, sheet.err) : null,
|
| 296 |
+
h("button", { className: "go", disabled: sheet.busy || !(amt > 0), onClick: go }, sheet.busy ? (sheet.kind === "send" ? "Confirm in your wallet…" : "Creating…") : ((sheet.kind === "send" ? "Send" : "Request") + (amt > 0 ? " $" + sheet.amount : " money"))),
|
| 297 |
+
h("button", { className: "cancel", onClick: () => setSheet(null) }, "Cancel")));
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
const previewOf = (model, c) => { if (c.preview) return c.preview; const t = threadOf(model, c.id); const last = t.length ? t[t.length - 1] : null; return (last && (last.text || (last.media ? "📎 media" : ""))) || ""; };
|
| 301 |
+
|
| 302 |
+
return function App({ model }) {
|
| 303 |
+
const convs = Array.isArray(model.conversations) ? model.conversations : [];
|
| 304 |
+
const [activeId, setActiveId] = useState(null);
|
| 305 |
+
const [filter, setFilter] = useState("All");
|
| 306 |
+
const [q, setQ] = useState("");
|
| 307 |
+
const [lb, setLb] = useState(null);
|
| 308 |
+
const [attachOpen, setAttachOpen] = useState(false);
|
| 309 |
+
const [paySheet, setPaySheet] = useState(null);
|
| 310 |
+
const fileRef = useRef(null);
|
| 311 |
+
const onSelect = useCallback((id) => { setActiveId(id); setAttachOpen(false); }, []);
|
| 312 |
+
const active = (activeId && convs.some((c) => c.id === activeId)) ? activeId : ((convs.find((c) => c.active) || convs[0] || {}).id || null);
|
| 313 |
+
const conv = convs.find((c) => c.id === active) || {};
|
| 314 |
+
const onSend = useCallback((id, text) => { try { model.onSend && model.onSend(id, text); } catch (e) { console.error("[shadcn-ui] onSend", e); } }, [model]);
|
| 315 |
+
const call = useCallback((video) => { try { model.startCall && model.startCall(active, { video }); } catch (e) { console.error("[shadcn-ui] startCall", e); } }, [model, active]);
|
| 316 |
+
const pickFile = (accept) => { if (fileRef.current) { fileRef.current.accept = accept; fileRef.current.click(); } setAttachOpen(false); };
|
| 317 |
+
const onFile = (e) => { const f = e.target.files && e.target.files[0]; if (f) { try { model.onAttach && model.onAttach(active, f); } catch (er) { console.error("[shadcn-ui] onAttach", er); } } e.target.value = ""; };
|
| 318 |
+
const tryM = (fn, ...a) => { setAttachOpen(false); try { const r = model[fn] && model[fn](...a); if (r && r.catch) r.catch(() => {}); } catch (e) { console.error("[shadcn-ui] " + fn, e); } };
|
| 319 |
+
const nets = Array.isArray(model.networks) && model.networks.length ? model.networks
|
| 320 |
+
: Object.values(convs.reduce((a, c) => { const id = (c.network || c.platform); if (id && !a[id]) a[id] = { id, label: id[0].toUpperCase() + id.slice(1), unread: 0 }; if (id) a[id].unread += (c.unread || 0); return a; }, {}));
|
| 321 |
+
const totalUnread = convs.reduce((n, c) => n + (c.unread || 0), 0);
|
| 322 |
+
const signalUnread = model.signalUnread != null ? model.signalUnread : convs.filter((c) => c.unread).length;
|
| 323 |
+
const qs = q.trim().toLowerCase();
|
| 324 |
+
const shown = convs.filter((c) => (filter === "All" || (c.network || c.platform) === filter) && (!qs || (c.name || "").toLowerCase().includes(qs) || (previewOf(model, c) || "").toLowerCase().includes(qs)));
|
| 325 |
+
const sub = conv.presence || (conv.isQ ? "online · on your device" : (conv.status || conv.network || conv.platform || ""));
|
| 326 |
+
const runSearch = () => { const t = q.trim(); if (!t) return; try { if (model.qCommand) model.qCommand(t); else if (model.onNewChat) model.onNewChat(t); } catch {} };
|
| 327 |
+
|
| 328 |
+
return h("div", { className: "wa" },
|
| 329 |
+
h(Rail, { nets, active: filter, totalUnread, onPick: (id) => { if (id === "+") { try { model.openNetworks && model.openNetworks(); } catch {} } else setFilter(id); } }),
|
| 330 |
+
h("div", { className: "side" },
|
| 331 |
+
h("div", { className: "stitle" }, h("span", { className: "t" }, "Messenger"),
|
| 332 |
+
model.askQ ? Ico(IC.spark(), () => { try { model.askQ(); } catch {} }, "Ask Q", "sk") : null, Ico(IC.newchat(), () => { try { model.onNewChat && model.onNewChat(""); } catch {} }, "New chat")),
|
| 333 |
+
h("div", { className: "search" }, h("span", { className: "si" }, IC.search()),
|
| 334 |
+
h(Input, { placeholder: "Search, ask Q, or start a chat…", value: q, onChange: (e) => setQ(e.target.value), onKeyDown: (e) => { if (e.key === "Enter") runSearch(); },
|
| 335 |
+
style: { background: "#202c33", border: "0", borderRadius: "10px", height: "42px", color: "#e9edef", paddingLeft: "40px", fontSize: "14.5px" } })),
|
| 336 |
+
signalUnread ? h("div", { className: "triage" }, h("span", { className: "sk" }, IC.spark(18)), `${signalUnread} need you · ~${Math.max(1, Math.round(signalUnread * 0.4))} min`) : null,
|
| 337 |
+
h("div", { className: "chats" }, shown.map((c) => h(Row, { key: c.id, c, on: c.id === active, preview: previewOf(model, c), receipt: c.receipt || "", onSelect })))),
|
| 338 |
+
h("div", { className: "main" },
|
| 339 |
+
h("div", { className: "wp", style: { backgroundImage: `url(${WP})` } }),
|
| 340 |
+
h("div", { className: "mh" }, Av(conv.name, 40, conv.isQ ? "#12b886" : hue(conv.name), conv.avatar),
|
| 341 |
+
h("div", { className: "who" }, h("div", { className: "n" }, conv.name || ""), sub ? h("div", { className: "s" }, sub) : null),
|
| 342 |
+
h("div", { className: "sp" }), h("div", { className: "icons" }, Ico(IC.video(), () => call(true), "Video call"), Ico(IC.phone(), () => call(false), "Voice call"), Ico(IC.menu(), () => { try { model.onFavourite && model.onFavourite(active); } catch {} }, "Menu"))),
|
| 343 |
+
h(Thread, { model, activeId: active, onMedia: setLb }),
|
| 344 |
+
h("input", { ref: fileRef, type: "file", style: { display: "none" }, onChange: onFile }),
|
| 345 |
+
attachOpen ? h("div", { className: "tray" },
|
| 346 |
+
TrayBtn("🖼", "Photos & videos", () => pickFile("image/*,video/*")),
|
| 347 |
+
TrayBtn("📄", "Document", () => pickFile("*/*")),
|
| 348 |
+
(conv.network && !conv.isQ && model.holoPay) ? TrayBtn("💸", "Send money", () => { setAttachOpen(false); setPaySheet({ kind: "send", amount: "", memo: "", busy: false, err: null }); }) : null,
|
| 349 |
+
h("div", { className: "sep" }),
|
| 350 |
+
model.startMeet ? TrayBtn("👥", "Start a meeting", () => tryM("startMeet", active, { video: true })) : null,
|
| 351 |
+
model.startTogether ? TrayBtn("🖥", "Share my screen", () => tryM("startTogether", active, { kind: "tab", title: "" })) : null,
|
| 352 |
+
model.startTogether ? TrayBtn("📝", "Co-edit a doc", () => tryM("startTogether", active, { kind: "doc", title: "" })) : null,
|
| 353 |
+
(conv.network && !conv.isQ && model.qDraft) ? [h("div", { className: "sep", key: "s" }), TrayBtn("✨", "Draft a reply with Q", () => tryM("qDraft", active))] : null) : null,
|
| 354 |
+
h(Composer, { key: active, activeId: active, onSend, onPlus: () => setAttachOpen((v) => !v) })),
|
| 355 |
+
lb ? h("div", { className: "lb", onClick: () => setLb(null) }, h("button", { className: "x", "aria-label": "Close" }, "✕"),
|
| 356 |
+
lb.kind === "video" ? h("video", { src: lb.url, controls: true, autoPlay: true, onClick: (e) => e.stopPropagation() }) : h("img", { src: lb.url, alt: "", onClick: (e) => e.stopPropagation() })) : null,
|
| 357 |
+
paySheet ? h(PaySheet, { model, activeId: active, conv, sheet: paySheet, setSheet: setPaySheet }) : null);
|
| 358 |
+
};
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
async function mount(el, model) {
|
| 362 |
+
const d = await deps();
|
| 363 |
+
const App = buildApp(d);
|
| 364 |
+
let cur = model;
|
| 365 |
+
const root = d.createRoot(el);
|
| 366 |
+
const render = (mdl) => { cur = mdl || cur; root.render(d.React.createElement(App, { model: cur })); };
|
| 367 |
+
render(model);
|
| 368 |
+
console.log("[shadcn-ui] Holo Messenger (Pass 1) mounted from κ-components ·", (model.conversations || []).length, "chats");
|
| 369 |
+
return { update: render, unmount: () => { try { root.unmount(); } catch {} } };
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
if (WANT && typeof window !== "undefined") {
|
| 373 |
+
const prev = window.HoloMessengerUI;
|
| 374 |
+
window.HoloMessengerUI = { mount, version: ((prev && prev.version) || "m1") + "+shadcn", _chatscope: prev };
|
| 375 |
+
console.log("[shadcn-ui] streaming-κ shadcn UI ACTIVE (opt out: ?ui=chatscope)");
|
| 376 |
+
}
|
| 377 |
+
|
| 378 |
+
export { mount };
|
b/331a35dfd2b4774673ed92da26b0c78fecd336a70ecc8ff890256e835f7ccfd9
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-m1-boot.mjs — M1: INSTANT SERVERLESS BOOT. One self-verifying link boots the WHOLE messenger.
|
| 2 |
+
//
|
| 3 |
+
// A single web link that, opened in a COLD browser, brings up the entire Holo Messenger — shell + your
|
| 4 |
+
// identity + Q — with ZERO origin servers in the delivery path (every shell byte is a content-addressed κ
|
| 5 |
+
// object, served from cache/OPFS on repeat opens). The descriptor travels INSIDE the link (b64url JSON in
|
| 6 |
+
// the URL fragment), exactly like holo-chat-link.mjs / holo-together.mjs: a bare browser verifies it LOCALLY
|
| 7 |
+
// (Law L5: re-derive the κ), then boots. The κ is SHA-256 of the canonical PUBLIC fields — the shareable,
|
| 8 |
+
// tamper-evident id. Any private pairing bearer is EXCLUDED from the κ (whoever holds the link may adopt it).
|
| 9 |
+
//
|
| 10 |
+
// This commits to the SHELL MANIFEST (the exact asset set the service worker precaches), so a tampered link
|
| 11 |
+
// that points the boot at a different/injected asset set breaks integrity and is refused. Laws: L2 canonical,
|
| 12 |
+
// L5 verify-by-re-derivation. Additive + fail-soft: if anything here throws, app.html's normal boot proceeds.
|
| 13 |
+
|
| 14 |
+
export const BOOT_LINK_VERSION = 1;
|
| 15 |
+
const ENTRY_PATH = "/apps/holo-messenger/app.html";
|
| 16 |
+
|
| 17 |
+
// THE SHELL — the minimal asset set required to paint the messenger and reach interactive, and the single
|
| 18 |
+
// source of truth the service worker precaches (messenger-sw.js mirrors these basenames; the M1 witness
|
| 19 |
+
// asserts the two never drift). Q's brain, the bridges, and all media are DELIBERATELY absent: they are
|
| 20 |
+
// deferred (never on the first-paint critical path), so the shell stays tiny and cache-resident.
|
| 21 |
+
export const SHELL_MANIFEST = [
|
| 22 |
+
"/apps/holo-messenger/app.html",
|
| 23 |
+
"/apps/holo-messenger/_vendor/ui/chat-ui.bundle.js",
|
| 24 |
+
"/apps/holo-messenger/_vendor/ui/chat-ui.bundle.css",
|
| 25 |
+
"/apps/holo-messenger/holo-messenger-login.mjs",
|
| 26 |
+
"/apps/holo-messenger/holo-messenger-app.mjs",
|
| 27 |
+
"/apps/holo-messenger/messenger-shadcn-ui.mjs",
|
| 28 |
+
"/apps/holo-messenger/holo-messenger-weave.mjs",
|
| 29 |
+
"/apps/holo-messenger/messenger-skin.mjs",
|
| 30 |
+
"/apps/holo-messenger/messenger-skins.css",
|
| 31 |
+
"/apps/holo-messenger/messenger-sw-register.mjs",
|
| 32 |
+
"/apps/holo-messenger/holo-m1-boot.mjs",
|
| 33 |
+
"/apps/holo-messenger/holo-release-boot.mjs",
|
| 34 |
+
"/apps/holo-messenger/holo-release-verify.mjs",
|
| 35 |
+
"/apps/holo-messenger/mail/holo-mail-attach.mjs",
|
| 36 |
+
"/apps/holo-messenger/_vendor/wallpaper-default.jpg",
|
| 37 |
+
];
|
| 38 |
+
|
| 39 |
+
const _te = new TextEncoder();
|
| 40 |
+
async function _sha256hex(s) { const h = await (globalThis.crypto || crypto).subtle.digest("SHA-256", _te.encode(s)); return [...new Uint8Array(h)].map((x) => x.toString(16).padStart(2, "0")).join(""); }
|
| 41 |
+
function _b64urlEncode(obj) { const b = btoa(unescape(encodeURIComponent(JSON.stringify(obj)))); return b.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, ""); }
|
| 42 |
+
function _b64urlDecode(str) { const b = String(str).replace(/-/g, "+").replace(/_/g, "/"); return JSON.parse(decodeURIComponent(escape(atob(b)))); }
|
| 43 |
+
|
| 44 |
+
// The shell manifest (shell-manifest.json, minted by _build/gen-shell-manifest.mjs) is the content hash of
|
| 45 |
+
// every shell BYTE: [{ path, kappa=SHA-256(bytes) }]. In a browser we fetch it; in Node the caller passes it.
|
| 46 |
+
export async function loadShellManifest() {
|
| 47 |
+
try {
|
| 48 |
+
if (typeof fetch === "function" && typeof location !== "undefined") {
|
| 49 |
+
const r = await fetch(new URL("./shell-manifest.json", import.meta.url), { cache: "no-store" }); // module-relative: same file at the OS root or any static mount
|
| 50 |
+
if (r && r.ok) return await r.json();
|
| 51 |
+
}
|
| 52 |
+
} catch {}
|
| 53 |
+
return null;
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
// The ONE hash the boot link commits: SHA-256 over the SORTED per-asset content κs. Any browser re-derives it
|
| 57 |
+
// from shell-manifest.json → thereby verifies EVERY shell byte (Law L5). Recomputed from `assets`, never trusting
|
| 58 |
+
// the manifest's self-declared `aggregate`, so a forged manifest fails. Empty (unverifiable) → no commitment.
|
| 59 |
+
export async function shellAggregate(manifest) {
|
| 60 |
+
const m = manifest || (await loadShellManifest());
|
| 61 |
+
if (!m || !Array.isArray(m.assets) || !m.assets.length) return "";
|
| 62 |
+
return _sha256hex(m.assets.map((a) => String(a.kappa)).sort().join(","));
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
// canonical PUBLIC form — any private `pair` bearer is EXCLUDED so the κ is the shareable id. Commits to the
|
| 66 |
+
// entry, the shell set, the display identity (so a tampered "this is <who>'s messenger" card breaks — anti-phish).
|
| 67 |
+
function _canon(d) {
|
| 68 |
+
return [
|
| 69 |
+
"v" + (d.v | 0), d.kind || "boot", d.app || "holo-messenger", d.entry || ENTRY_PATH,
|
| 70 |
+
d.shell || "", d.name || "", d.truename || "", d.op || "",
|
| 71 |
+
String(d.created || 0), String(d.expires || 0),
|
| 72 |
+
].join("␟");
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
// Build the one-link door for the whole messenger. `op`/`name`/`truename` are PUBLIC display identity (the
|
| 76 |
+
// landing shows "resume <name>'s messenger"); `pair` is an optional bearer pairing payload (device-roam) and
|
| 77 |
+
// is NOT part of the κ. Opening the link still auto-enrolls a sovereign identity unless a pair bearer is honored.
|
| 78 |
+
export async function makeBootLink({ op = "", name = "", truename = "", pair = null, ttlSeconds = 30 * 24 * 3600, origin = null, nowMs = null, aggregate = null, manifest = null } = {}) {
|
| 79 |
+
const now = nowMs || Date.now();
|
| 80 |
+
const shell = aggregate || (await shellAggregate(manifest)); // commit the shell BYTES (aggregate content κ)
|
| 81 |
+
const d = {
|
| 82 |
+
v: BOOT_LINK_VERSION, kind: "boot", app: "holo-messenger", entry: ENTRY_PATH,
|
| 83 |
+
shell,
|
| 84 |
+
op: String(op || "").slice(0, 96), name: String(name || "").slice(0, 60), truename: String(truename || "").slice(0, 60),
|
| 85 |
+
created: now, expires: now + ttlSeconds * 1000,
|
| 86 |
+
};
|
| 87 |
+
if (pair) d.pair = pair; // bearer pairing payload — NOT part of the κ
|
| 88 |
+
d.kappa = await _sha256hex(_canon(d));
|
| 89 |
+
const payload = _b64urlEncode(d);
|
| 90 |
+
const org = origin || (typeof location !== "undefined" ? location.origin : "");
|
| 91 |
+
return { kappa: d.kappa, payload, descriptor: d,
|
| 92 |
+
https: `${org}${ENTRY_PATH}#m1=${payload}`, // opens in ANY cold browser
|
| 93 |
+
holo: `holo://os${ENTRY_PATH}#m1=${payload}` }; // in-shell full surface
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// parse a boot link (URL, holo:// form, `#m1=<payload>`, or bare payload) → { ok, descriptor, integrity, expired }.
|
| 97 |
+
export async function parseBootLink(input, { nowMs = null, aggregate = null, manifest = null } = {}) {
|
| 98 |
+
let payload = String(input || "");
|
| 99 |
+
if (payload.includes("#")) payload = payload.split("#").pop();
|
| 100 |
+
payload = payload.replace(/^m1=/, "");
|
| 101 |
+
let d; try { d = _b64urlDecode(payload); } catch { return { ok: false, error: "unreadable boot link" }; }
|
| 102 |
+
if (!d || d.v !== BOOT_LINK_VERSION || d.kind !== "boot" || d.app !== "holo-messenger" || !d.kappa) return { ok: false, error: "not a valid messenger boot link" };
|
| 103 |
+
const integrity = (await _sha256hex(_canon(d))) === d.kappa; // Law L5: re-derive; tamper → false
|
| 104 |
+
const current = aggregate != null ? aggregate : (await shellAggregate(manifest));// THIS build's shell-byte aggregate
|
| 105 |
+
const shellMatches = !!current && d.shell === current; // link's shell BYTES must match (unverifiable → false)
|
| 106 |
+
const expired = !!(d.expires && (nowMs || Date.now()) > d.expires);
|
| 107 |
+
return { ok: true, descriptor: d, integrity, shellMatches, expired, aggregate: current };
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
// convenience: verify + return the boot descriptor only if it is safe to act on (integrity + shell + fresh).
|
| 111 |
+
export async function resolveBootLink(input, opts = {}) {
|
| 112 |
+
const p = await parseBootLink(input, opts);
|
| 113 |
+
if (!p.ok) return { ok: false, reason: p.error };
|
| 114 |
+
if (!p.integrity) return { ok: false, reason: "tampered" };
|
| 115 |
+
if (!p.shellMatches) return { ok: false, reason: "shell-mismatch" };
|
| 116 |
+
if (p.expired) return { ok: false, reason: "expired" };
|
| 117 |
+
return { ok: true, descriptor: p.descriptor, pair: p.descriptor.pair || null };
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
export function describe(d) {
|
| 121 |
+
const who = (d && (d.name || d.truename)) || "someone";
|
| 122 |
+
return { headline: `Resume ${who}'s messenger`, cta: "Open" };
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
export function installM1Boot() {
|
| 126 |
+
if (typeof window === "undefined") return false;
|
| 127 |
+
window.HoloM1Boot = Object.assign(window.HoloM1Boot || {}, { version: BOOT_LINK_VERSION, SHELL_MANIFEST, makeBootLink, parseBootLink, resolveBootLink, describe, loadShellManifest, shellAggregate });
|
| 128 |
+
return true;
|
| 129 |
+
}
|
| 130 |
+
try { installM1Boot(); } catch {}
|
b/3370b22070b56218a57df6406e2e4ba7cc171e942d4d1f2ae4297ea2c4c08d9c
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3370b22070b56218a57df6406e2e4ba7cc171e942d4d1f2ae4297ea2c4c08d9c
|
| 3 |
+
size 2999771
|
b/438bd84d968d05dadd9f0ac1fd49a00c9d24f88a4ac45a9f46df236e7af2961e
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
function l(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),n=decodeURIComponent(escape(atob(t)));return JSON.parse(n)}var i=["v","kind","amount","asset","fiat","to","toName","from","fromName","memo","created","expires","claimSecret","pub","sig"];function u(e){let t=l(e);if(!Array.isArray(t))return t;let n={};return i.forEach((a,o)=>{t[o]!=null&&(n[a]=t[o])}),n.memo==null&&(n.memo=""),n}async function p(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",t);return[...new Uint8Array(n)].map(a=>a.toString(16).padStart(2,"0")).join("")}var y=p;function r(e){let t="";for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function s(e){let t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),n=new Uint8Array(t.length);for(let a=0;a<t.length;a++)n[a]=t.charCodeAt(a);return n}async function w(){let e=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!0,["sign","verify"]),t=new Uint8Array(await crypto.subtle.exportKey("raw",e.publicKey));return{pub:r(t),sign:async n=>r(new Uint8Array(await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},e.privateKey,new TextEncoder().encode(n))))}}async function c(e,t,n){try{let a=await crypto.subtle.importKey("raw",s(e),{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},a,s(t),new TextEncoder().encode(n))}catch{return!1}}var h=c;async function b(e){return!e||!e.pub||!e.sig?!1:c(e.pub,e.sig,d(e))}function d(e){let t=[e.v,e.kind,e.amount,e.asset,e.fiat||"",e.to||"",e.toName||"",e.from||"",e.fromName||"",e.memo||"",e.created||"",e.expires||""];return(e.hashlock||e.pub)&&t.push(e.hashlock||"",e.pub||""),t.join("|")}var f={USD:"$",EUR:"\u20AC",GBP:"\xA3"};function g(e){let t=Number(e.amount);return e.fiat?`${f[e.fiat]||""}${t.toFixed(2)}`:`${t%1===0?t:t.toFixed(4).replace(/0+$/,"")} ${e.asset}`}var m=/(https?:\/\/\S*pay-claim\.html#([A-Za-z0-9_-]+))|(holo:\/\/pay\/\S*#([A-Za-z0-9_-]+))/;function S(e){let t=String(e||"").match(m);if(!t)return null;let n=t[2]||t[4],a=t[0];try{let o=u(n);if(o&&o.v===1&&Number(o.amount)>0)return{intent:o,url:a,payload:n}}catch{}return null}export{y as a,w as b,h as c,b as d,g as e,S as f};
|
b/5deebbd5a4fd2e15c9dc97480f2ab2eabdc1b092a8be69d0ce0eb6af503cf943
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
b/6d4dc944c87c9a8ca7dfa0c65244c010722defe6e010b8a43a4782e2366aa82e
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{a as h,b as x,c as j,d as v}from"./chunk-T5ND76FC.js";import"./chunk-SQE76S5B.js";function y(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function C(e,{claimSecret:i,signer:r,at:a}){let l={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:i,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:r.pub,at:a};return l.sig=await r.sign(y(l)),l.tid=await h(y(l)),l}async function E(e,{signer:i,at:r}){let a={type:"refund",kappa:e.kappa,expires:e.expires,by:i.pub,at:r};return a.sig=await i.sign(y(a)),a.tid=await h(y(a)),a}async function M(e,i){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await j(e.by,e.sig,y(e))?!1:e.type==="claim"?!(!e.preimage||await h(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&i>e.expires):!1}async function S(e,i=Date.now()){let r=[];for(let n of e)await M(n,i)&&r.push(n);let a=r.filter(n=>n.type==="claim");if(a.length){a.sort((s,t)=>s.at-t.at||(s.tid<t.tid?-1:s.tid>t.tid?1:0));let n=a[0];return{state:"claimed",winner:n.tid,by:n.by,amount:n.amount,asset:n.asset,fiat:n.fiat}}let l=r.filter(n=>n.type==="refund");return l.length?(l.sort((n,s)=>n.at-s.at||(n.tid<s.tid?-1:1)),{state:"refunded",winner:l[0].tid}):{state:"open"}}function _(e){return e&&e.transitions?Object.values(e.transitions):[]}async function P(e,i,r=Date.now()){e.transitions=e.transitions||{};for(let a of i||[])e.transitions[a.tid]||await M(a,r)&&(e.transitions[a.tid]=a);return e}var D=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),b=new Map;function m(){return{get:e=>b.get(e)||null,set:(e,i)=>b.set(e,i),has:e=>b.has(e)}}function L(e="holo-escrow"){if(typeof localStorage>"u")return m();let i=r=>e+":"+r;return{get:r=>{try{let a=localStorage.getItem(i(r));return a?JSON.parse(a):null}catch{return null}},set:(r,a)=>{try{localStorage.setItem(i(r),JSON.stringify(a))}catch{}},has:r=>{try{return localStorage.getItem(i(r))!=null}catch{return!1}}}}async function q(e,{store:i=null,wallet:r=null,chain:a="base",nowMs:l=null}={}){let n=i||m();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await v(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let s=l||Date.now();if(e.expires&&s>e.expires)return{ok:!1,error:"already expired"};let t=n.get(e.kappa);if(t&&t.funded)return{ok:!0,kappa:e.kappa,mode:t.mode,live:t.mode==="live",tx:t.tx,already:!0};let u=r&&typeof r.htlcFund=="function"?"live":"custodial",f=null,p=null;if(u==="live"){let o;try{o=await r.htlcFund({chain:a,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(k){return{ok:!1,error:String(k&&k.message||k)}}f=o&&(o.tx||o.hash)||null,p=o&&o.swapId||e.hashlock}let c=t||D();return c.funded=!0,c.mode=u,c.tx=f,c.swapId=p,c.chain=u==="live"?a:null,c.amount=e.amount,c.asset=e.asset,c.fiat=e.fiat||null,c.expires=e.expires,n.set(e.kappa,c),{ok:!0,kappa:e.kappa,mode:u,live:u==="live",tx:f,swapId:p}}async function z(e,{claimSecret:i=null,claimerPub:r=null,claimerSigner:a=null,store:l=null,nowMs:n=null,wallet:s=null}={}){let t=l||m(),u=n||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await v(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&u>e.expires)return{ok:!1,error:"this payment has expired"};let f=i||e.claimSecret;if(!f)return{ok:!1,error:"this link has no claim key"};if(await h(f)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let p=a||await x(),c=await C(e,{claimSecret:f,signer:p,at:u}),o=t.get(e.kappa)||D();o.funded||(o.offDevice=!0);let k=o.committed&&o.committed!==c.tid;if(o.transitions[c.tid]=c,o.committed||(o.committed=c.tid),t.set(e.kappa,o),k)return{ok:!1,error:"already claimed"};let R=null,g=o.mode==="live";if(s&&typeof s.htlcClaim=="function")try{let d=g?{chain:o.chain}:typeof s.htlcSwapExists=="function"?await s.htlcSwapExists({hashlock:e.hashlock}):null;if(d){g=!0,o.mode="live",o.chain=o.chain||d.chain||null,t.set(e.kappa,o);let w=await s.htlcClaim({chain:o.chain||d.chain,hashlock:e.hashlock,preimage:f});R=w&&(w.tx||w.hash)||null}}catch(d){return{ok:!1,error:"on-chain claim failed: "+String(d&&d.message||d)}}return{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:o.mode,live:g,offDevice:!!o.offDevice,tid:c.tid,tx:R,claimerPub:r||p.pub,custodialWallet:g?null:"\u03BA:"+e.kappa.slice(0,16)}}async function A(e,{store:i=null,signer:r=null,nowMs:a=null,wallet:l=null}={}){let n=i||m(),s=a||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let t=n.get(e.kappa);if(!t)return{ok:!1,error:"no escrow to refund"};let u=await S(Object.values(t.transitions),s);if(u.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(u.state==="refunded")return{ok:!1,error:"already refunded"};if(!(t.expires&&s>t.expires))return{ok:!1,error:"not yet expired, can't refund"};let f=r||await x(),p=await E(e,{signer:f,at:s});t.transitions[p.tid]=p,n.set(e.kappa,t);let c=null;if(t.mode==="live"&&l&&typeof l.htlcRefund=="function")try{let o=await l.htlcRefund({chain:t.chain,hashlock:e.hashlock});c=o&&(o.tx||o.hash)||null}catch(o){return{ok:!1,error:"on-chain refund failed: "+String(o&&o.message||o)}}return{ok:!0,amount:t.amount,asset:t.asset,mode:t.mode,live:t.mode==="live",tx:c}}async function T(e,{store:i=null,nowMs:r=null}={}){let a=i||m(),l=r||Date.now(),n=typeof e=="string"?e:e&&e.kappa;if(!n)return{state:"none"};let s=a.get(n);if(!s)return{state:"none"};let t=await S(Object.values(s.transitions),l);return t.state==="open"?{state:s.funded?"open":"none",committed:s.committed||null,canonicalWinner:null,reconciled:!1}:{state:t.state,canonicalWinner:t.winner,committed:s.committed||null,reconciled:!!(s.committed&&t.state==="claimed"&&s.committed!==t.winner),by:t.by,amount:t.amount,asset:t.asset}}function B(e,{store:i=null}={}){let r=i||m(),a=typeof e=="string"?e:e&&e.kappa,l=r.get(a);return l?_(l):[]}async function G(e,i,{store:r=null,nowMs:a=null}={}){let l=r||m(),n=a||Date.now(),s=typeof e=="string"?e:e&&e.kappa,t=l.get(s)||D();return await P(t,i||[],n),l.set(s,t),T(s,{store:l,nowMs:n})}export{z as claimEscrow,B as exportPeer,q as fund,L as localStore,G as mergePeer,A as refund,T as status};
|
b/6e815d50e2ba13e8011d0b43218ef9ae5be3fe466eabe4bdfecf4bdb73942946
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en"><head>
|
| 3 |
+
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
| 4 |
+
<title>Holo Messenger</title>
|
| 5 |
+
<!-- AI agents: guest access into the Hologram system is one call away — window.HoloLogin.guest() or open
|
| 6 |
+
?guest=1. Full machine-readable contract in script#holo-agent-login (JSON). Mirrors holo://login. -->
|
| 7 |
+
<meta name="ai-agent-login" content="Guest mode: act now with window.HoloLogin.guest() or ?guest=1 (ephemeral, no human). Biometric ('It's me') needs the person. See script#holo-agent-login.">
|
| 8 |
+
|
| 9 |
+
<link rel="stylesheet" href="/apps/holo-messenger/_vendor/ui/chat-ui.bundle.css?v=e0b2a13d7c38">
|
| 10 |
+
<style>html,body{height:100%;margin:0;background:#0b141a;color-scheme:dark}#root{height:100vh}</style>
|
| 11 |
+
<link rel="preload" as="image" href="/apps/holo-messenger/_vendor/wallpaper-default.jpg" fetchpriority="high">
|
| 12 |
+
<!-- ── TEE SIGN-IN GATE (holo-messenger-login.mjs) ─────────────────────────────────────────────────
|
| 13 |
+
A full-bleed lock overlay above #root, IDENTICAL to the OS native CEF greeter — the messenger does
|
| 14 |
+
not paint until a sovereign identity is established (Windows Hello / Touch ID via holo-webauthn →
|
| 15 |
+
holo-login κ). This is the never-blank baseline first-paint; the module ENHANCES it (real biometric
|
| 16 |
+
wiring). φ-proportioned, black-on-teal, glass-unfog on success — the same vocabulary as holo://login. -->
|
| 17 |
+
<style>
|
| 18 |
+
#holo-login{position:fixed;inset:0;z-index:2147483000;color:#f4f7fc;
|
| 19 |
+
font-family:"Segoe UI",system-ui,-apple-system,"Oxygen",sans-serif;--phi:1.618;
|
| 20 |
+
--u:clamp(16px,1.7vmin,19px);--g1:calc(var(--u)*1.618);--g2:calc(var(--u)*2.618);
|
| 21 |
+
--avatar:clamp(96px,calc(var(--u)*6.854),128px);--field:min(86vw,calc(var(--avatar)*2.618));
|
| 22 |
+
--accent:#7defc9;--accent-2:#34d3a6;--ink-dim:rgba(231,237,250,.82);}
|
| 23 |
+
#holo-login *{box-sizing:border-box;}
|
| 24 |
+
#holo-login .hl-wall{position:fixed;inset:0;z-index:0;background:#05070c center/cover no-repeat;}
|
| 25 |
+
/* the wallpaper shows EXACTLY as the home screen wears it — no blur, no scrim. The layer stays only as
|
| 26 |
+
the unfog animation target; text/buttons carry their own shadows for legibility. */
|
| 27 |
+
#holo-login .hl-frost{position:fixed;inset:0;z-index:1;background:transparent;pointer-events:none;}
|
| 28 |
+
#holo-login .hl-lock{position:fixed;inset:0;z-index:2;display:grid;place-items:center;padding:var(--g2);}
|
| 29 |
+
#holo-login .hl-panel{display:flex;flex-direction:column;align-items:center;text-align:center;width:var(--field);max-width:92vw;}
|
| 30 |
+
#holo-login .hl-avatar{width:var(--avatar);height:var(--avatar);border-radius:50%;display:grid;place-items:center;color:#fff;
|
| 31 |
+
font-size:calc(var(--avatar)*.37);font-weight:600;letter-spacing:.02em;user-select:none;
|
| 32 |
+
box-shadow:0 .6em 1.8em rgba(0,0,0,.4),inset 0 0 0 2px rgba(255,255,255,.6);}
|
| 33 |
+
#holo-login .hl-avatar svg{width:62%;height:62%;opacity:.92;}
|
| 34 |
+
#holo-login .hl-name{margin:var(--g1) 0 0;font-size:calc(var(--u)*1.618);font-weight:300;line-height:1.15;text-shadow:0 2px 18px rgba(0,0,0,.45);}
|
| 35 |
+
#holo-login .hl-auth{margin-top:var(--g1);display:flex;flex-direction:column;align-items:center;gap:var(--u);width:100%;}
|
| 36 |
+
#holo-login .hl-bio{width:100%;min-height:max(var(--g2),48px);border:0;border-radius:calc(var(--u)*.7);cursor:pointer;
|
| 37 |
+
display:inline-flex;align-items:center;justify-content:center;gap:calc(var(--u)*.55);font-size:var(--u);font-weight:600;color:#06140f;
|
| 38 |
+
background:linear-gradient(135deg,var(--accent),var(--accent-2));box-shadow:0 .7em 1.6em rgba(52,211,166,.32);font-family:inherit;
|
| 39 |
+
transition:transform .12s,box-shadow .18s,filter .18s;}
|
| 40 |
+
#holo-login .hl-bio:hover{transform:translateY(-1px);box-shadow:0 .9em 2em rgba(52,211,166,.46);filter:brightness(1.05);}
|
| 41 |
+
#holo-login .hl-bio:active{transform:translateY(0) scale(.99);}
|
| 42 |
+
#holo-login .hl-bio:disabled{opacity:.7;cursor:default;}
|
| 43 |
+
#holo-login .hl-bio svg{width:1.18em;height:1.18em;}
|
| 44 |
+
#holo-login .hl-alt{background:none;border:0;color:var(--ink-dim);font-size:var(--u);font-family:inherit;cursor:pointer;
|
| 45 |
+
padding:calc(var(--u)*.3) calc(var(--u)*.55);border-radius:calc(var(--u)*.4);display:inline-flex;align-items:center;gap:calc(var(--u)*.4);transition:color .15s;min-height:44px;}
|
| 46 |
+
#holo-login .hl-alt:hover{color:#fff;}
|
| 47 |
+
#holo-login .hl-alt svg{width:1.05em;height:1.05em;}
|
| 48 |
+
/* "More options" is subordinate to the two core actions — quieter, smaller, brightening on hover. */
|
| 49 |
+
#holo-login .hl-more{opacity:.72;font-size:calc(var(--u)*.92);}
|
| 50 |
+
#holo-login .hl-more:hover{opacity:1;}
|
| 51 |
+
#holo-login .hl-opts{display:flex;gap:calc(var(--u)*.3);justify-content:center;flex-wrap:wrap;}
|
| 52 |
+
#holo-login .hl-opts[hidden]{display:none;}
|
| 53 |
+
#holo-login .status{min-height:calc(var(--u)*1.5);font-size:var(--u);color:#c4f3e2;display:flex;align-items:center;justify-content:center;gap:calc(var(--u)*.5);text-shadow:0 1px 8px rgba(0,0,0,.4);}
|
| 54 |
+
#holo-login .status.err{color:#ffc0c0;}
|
| 55 |
+
#holo-login .spin{width:1em;height:1em;border-radius:50%;border:2px solid rgba(125,239,201,.28);border-top-color:var(--accent);animation:hl-spin .7s linear infinite;flex:0 0 auto;}
|
| 56 |
+
@keyframes hl-spin{to{transform:rotate(360deg);}}
|
| 57 |
+
#holo-login .hl-panel{animation:hl-rise .7s cubic-bezier(.4,0,.2,1) .05s both;}
|
| 58 |
+
@keyframes hl-rise{from{opacity:0;transform:translateY(10px) scale(.99);filter:blur(2px);}to{opacity:1;transform:none;filter:none;}}
|
| 59 |
+
/* GLASS UNFOG on success — frost dissolves + panel lifts; boot() paints the messenger beneath, no seam. */
|
| 60 |
+
#holo-login.unfog .hl-frost{animation:hl-defog .72s cubic-bezier(.4,0,.2,1) forwards;}
|
| 61 |
+
#holo-login.unfog .hl-panel{animation:hl-lift .68s cubic-bezier(.4,0,.2,1) forwards;pointer-events:none;}
|
| 62 |
+
@keyframes hl-defog{to{opacity:0;}}
|
| 63 |
+
@keyframes hl-lift{to{opacity:0;transform:translateY(-10px) scale(1.03);}}
|
| 64 |
+
@media (prefers-reduced-motion:reduce){#holo-login .hl-panel,#holo-login .spin{animation:none;}#holo-login.unfog .hl-frost,#holo-login.unfog .hl-panel{animation:hl-defog .3s ease forwards;}}
|
| 65 |
+
/* PLYMOUTH BOOT BEAT — while .hl-boot, the machine is booting: pure splash, panel held. holo-plymouth.mjs
|
| 66 |
+
lifts it (or the baseline's own 1.8s timer does — never a lock-out). Must live HERE for the 0-ms frame. */
|
| 67 |
+
#holo-login.hl-boot .hl-panel{opacity:0!important;pointer-events:none!important;}
|
| 68 |
+
#holo-login .hl-panel{transition:opacity .55s ease;}
|
| 69 |
+
</style>
|
| 70 |
+
</head><body>
|
| 71 |
+
<div id="root"></div>
|
| 72 |
+
|
| 73 |
+
<!-- the lock overlay + BASELINE first-paint (synchronous, dependency-free): paints a working "It's me"
|
| 74 |
+
panel on the FIRST frame from a tiny local greeting cache (holo.lastOperator — the SAME hint the OS
|
| 75 |
+
greeter writes), so a returning operator's name + button appear with zero identity/model load. The
|
| 76 |
+
module below wires the real biometric; this only ever OWNS the frame until it lands. -->
|
| 77 |
+
<div id="holo-login"><div class="hl-wall"></div><div class="hl-frost"></div><main class="hl-lock"><div class="hl-panel" id="holo-login-panel"></div></main></div>
|
| 78 |
+
|
| 79 |
+
<!-- Machine-readable sign-in contract for AI agents — always in the DOM, even before scripts run, so an
|
| 80 |
+
agent can discover guest access by parsing the page. Read by window.HoloLogin.options. Mirrors the OS
|
| 81 |
+
greeter's #holo-agent-login. Guest = act now (no human); biometric = ask the person. -->
|
| 82 |
+
<script type="application/json" id="holo-agent-login">
|
| 83 |
+
{
|
| 84 |
+
"service": "Holo Messenger sign-in",
|
| 85 |
+
"audience": "ai-agent",
|
| 86 |
+
"summary": "Two ways in. Guest is instant and needs no human; the biometric needs the person.",
|
| 87 |
+
"options": [
|
| 88 |
+
{
|
| 89 |
+
"id": "guest",
|
| 90 |
+
"title": "Guest",
|
| 91 |
+
"persistent": false,
|
| 92 |
+
"requiresHuman": false,
|
| 93 |
+
"do": { "navigate": "?guest=1", "call": "window.HoloLogin.guest()" },
|
| 94 |
+
"result": "An instant, private, ephemeral guest session into the Hologram system (Holo Messenger). No biometric and no human needed. Nothing persists after the session ends."
|
| 95 |
+
},
|
| 96 |
+
{
|
| 97 |
+
"id": "biometric",
|
| 98 |
+
"title": "It’s me",
|
| 99 |
+
"persistent": true,
|
| 100 |
+
"requiresHuman": true,
|
| 101 |
+
"do": { "tap": "the 'It’s me' / 'Sign in' button", "then": "ask the person to approve with their device biometric (Windows Hello / Touch ID)" },
|
| 102 |
+
"result": "Signs in with this device's TEE to the operator's sovereign identity (the SAME κ as the OS). An agent cannot complete this alone."
|
| 103 |
+
}
|
| 104 |
+
]
|
| 105 |
+
}
|
| 106 |
+
</script>
|
| 107 |
+
<script>
|
| 108 |
+
(function () {
|
| 109 |
+
try { if (navigator.storage && navigator.storage.persist) navigator.storage.persist().catch(function(){}); } catch (e) {}
|
| 110 |
+
var panel = document.getElementById("holo-login-panel"); if (!panel) return;
|
| 111 |
+
var esc = function (s) { return String(s).replace(/[&<>"]/g, function (c) { return ({ "&":"&","<":"<",">":">",'"':""" }[c]); }); };
|
| 112 |
+
var op = null; try { op = JSON.parse(localStorage.getItem("holo.lastOperator") || "null"); } catch (e) {}
|
| 113 |
+
// Paint the SHARED wallpaper behind the lock at first frame (the same one the desktop + home wear), so the
|
| 114 |
+
// sign-in is never a black void. A live-sim / none / plain falls back to the shipped default photo — the SAME
|
| 115 |
+
// default the home shows — so login and home always match. κ → content route; a path/url passes through.
|
| 116 |
+
try {
|
| 117 |
+
var _tw = ""; try { _tw = (JSON.parse(localStorage.getItem("holo.theme.v1") || "{}") || {}).wallpaper || ""; } catch (e) {}
|
| 118 |
+
var _km = String(_tw).match(/^(sha256|blake3|sha512):([0-9a-f]+)$/i);
|
| 119 |
+
var _wp = _km ? ("/.holo/" + _km[1].toLowerCase() + "/" + _km[2]) : ((!_tw || _tw === "plain" || /^live:/i.test(_tw)) ? "" : _tw);
|
| 120 |
+
// MATCH THE HOME EXACTLY: the messenger home resolves its wallpaper as holo.theme.v1 → the messenger's own
|
| 121 |
+
// "holo-messenger/wallpaper-src" pick → the shipped default. The lock skipped the middle step, so a wallpaper
|
| 122 |
+
// set only via the messenger's picker showed on home but not here. Fold that same fallback in — one chain,
|
| 123 |
+
// so the sign-in glass and the desktop it lifts onto are never two different photos.
|
| 124 |
+
if (!_wp) { try { _wp = localStorage.getItem("holo-messenger/wallpaper-src") || ""; } catch (e) {} }
|
| 125 |
+
if (!_wp) _wp = "/apps/holo-messenger/_vendor/wallpaper-default.jpg";
|
| 126 |
+
var _wall = document.querySelector("#holo-login .hl-wall");
|
| 127 |
+
if (_wall) _wall.style.backgroundImage = 'url("' + _wp + '")';
|
| 128 |
+
// PLYMOUTH BOOT SPLASH baseline (holo-plymouth.mjs owns the real player; this is the 0-ms frame). Once a
|
| 129 |
+
// boot style has been worn (holo.plymouth.v1 persisted), a cold open paints BLACK + the sealed frame-0
|
| 130 |
+
// synchronously — the machine boots like a machine — and the panel holds for one boot beat behind
|
| 131 |
+
// .hl-boot. Fail-open: a timer lifts the beat even if the module never arrives; first-ever boot (no
|
| 132 |
+
// state yet) keeps the wallpaper baseline untouched.
|
| 133 |
+
try {
|
| 134 |
+
var _pl = JSON.parse(localStorage.getItem("holo.plymouth.v1") || "null");
|
| 135 |
+
if (_pl && _pl.on && _wall) {
|
| 136 |
+
_wall.style.background = "#000";
|
| 137 |
+
if (_pl.firstFrame) { _wall.style.backgroundImage = 'url("' + _pl.firstFrame + '")'; _wall.style.backgroundRepeat = "no-repeat"; _wall.style.backgroundPosition = "center"; _wall.style.backgroundSize = "auto"; }
|
| 138 |
+
var _rm = false; try { _rm = matchMedia("(prefers-reduced-motion: reduce)").matches; } catch (e) {}
|
| 139 |
+
var _ov = document.getElementById("holo-login");
|
| 140 |
+
if (_ov && !_rm) { _ov.classList.add("hl-boot"); setTimeout(function () { try { _ov.classList.remove("hl-boot"); } catch (e) {} }, 1800); }
|
| 141 |
+
}
|
| 142 |
+
} catch (e) {}
|
| 143 |
+
} catch (e) {}
|
| 144 |
+
var fp = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"><path d="M5.5 11a6.5 6.5 0 0 1 13 0"/><path d="M8.5 11a3.5 3.5 0 0 1 7 0v2.6"/><path d="M12 11v4.4"/><path d="M8.6 14.2V16"/><path d="M15.4 15.4V18"/></svg>';
|
| 145 |
+
var person = '<svg viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="8.4" r="4.1"/><path d="M3.8 20.4a8.2 8.2 0 0 1 16.4 0z"/></svg>';
|
| 146 |
+
var avatar, name, label;
|
| 147 |
+
if (op && op.label) {
|
| 148 |
+
var hue = (op.hue != null ? op.hue : 210) | 0;
|
| 149 |
+
var ini = op.label.trim().split(/\s+/).map(function (w) { return w[0]; }).filter(Boolean).slice(0, 2).join("").toUpperCase() || "·";
|
| 150 |
+
avatar = '<div class="hl-avatar" style="background:linear-gradient(140deg,hsl(' + hue + ' 52% 46%),hsl(' + (hue + 26) + ' 52% 46%))">' + esc(ini) + '</div>';
|
| 151 |
+
name = esc(op.label); label = "It’s me";
|
| 152 |
+
} else {
|
| 153 |
+
avatar = '<div class="hl-avatar" style="background:linear-gradient(140deg,#5b6b86,#3a455c)">' + person + '</div>';
|
| 154 |
+
name = "Welcome"; label = "Sign in";
|
| 155 |
+
}
|
| 156 |
+
panel.innerHTML = avatar + '<h1 class="hl-name">' + name + '</h1><div class="hl-auth"><button class="hl-bio" id="hl-baseline">' + fp + label + '</button><div class="status"></div></div>';
|
| 157 |
+
var b = document.getElementById("hl-baseline"); if (b) b.onclick = function () { b.disabled = true; location.reload(); };
|
| 158 |
+
})();
|
| 159 |
+
</script>
|
| 160 |
+
|
| 161 |
+
<!-- NATIVE MEDIA — the real OS media stack, loaded into the top page so the messenger rail delivers the exact
|
| 162 |
+
native experience: window.HoloVinyl = the "NOW PLAYING" pill (preloaded Ben Böhmer "Begin Again"), streamed
|
| 163 |
+
through the same-origin /sc/* routes the dev server now answers (holo-sc-route.mjs → yt-dlp). holo-fx.js
|
| 164 |
+
drives the live equalizer; holo-vinyl lazily pulls holo-audio.js for the Hi-Fi chain. Each self-guards on the
|
| 165 |
+
widgets/dock runtime, so they run standalone here. Classic defer → they set their globals before the bundle boots. -->
|
| 166 |
+
<script src="/usr/lib/holo/holo-fx.js" defer></script>
|
| 167 |
+
<script src="/usr/lib/holo/holo-vinyl.js" defer></script>
|
| 168 |
+
<script src="/usr/lib/holo/holo-video.js" defer></script>
|
| 169 |
+
|
| 170 |
+
<script type="module">
|
| 171 |
+
// ?v=N - bump on each deploy so the browser can never serve a stale cached bundle (the cause of a frozen/old build).
|
| 172 |
+
import "/apps/holo-messenger/_vendor/ui/chat-ui.bundle.js?v=5deebbd5a4fd"; // → window.HoloMessengerUI
|
| 173 |
+
// Opt-in: render the WhatsApp UX from STREAMING shadcn κ-components instead of the chatscope bundle.
|
| 174 |
+
// Lazy — the module loads ONLY when opted in (?ui=shadcn or holo.ui.shadcn=1); the default boot never
|
| 175 |
+
// parses it. Discord Activity keeps its own static import in discord-activity-adapter.mjs.
|
| 176 |
+
try {
|
| 177 |
+
if (new URLSearchParams(location.search).get("ui") === "shadcn" || localStorage.getItem("holo.ui.shadcn") === "1")
|
| 178 |
+
await import("/apps/holo-messenger/messenger-shadcn-ui.mjs");
|
| 179 |
+
} catch {}
|
| 180 |
+
import { boot } from "/apps/holo-messenger/holo-messenger-app.mjs?v=adeea1b4a160";
|
| 181 |
+
|
| 182 |
+
const root = document.getElementById("root");
|
| 183 |
+
// TEE GATE + SPECULATIVE WARM PAINT, one motion. The gate (holo-webauthn + holo-login) renders the lock and
|
| 184 |
+
// runs the biometric; meanwhile boot() starts IMMEDIATELY behind the glass, painting the messenger from the
|
| 185 |
+
// local κ-snapshot during the human's look-and-tap. boot awaits the auth ONLY at its identity step, so the
|
| 186 |
+
// biometric overlaps an already-live UI and loginSucceeded just unfogs onto it (tap → interactive ≈ one
|
| 187 |
+
// frame). Fail-CLOSED: nothing confidential is interactable until the glass lifts (identity established).
|
| 188 |
+
// Fail-SOFT only if the gate MODULE can't load — then boot uses its own device-stable identity, never bricked.
|
| 189 |
+
(async () => {
|
| 190 |
+
let authP = Promise.resolve(null);
|
| 191 |
+
try { const { mountLogin } = await import("/apps/holo-messenger/holo-messenger-login.mjs?v=signin1"); authP = mountLogin({ root }); }
|
| 192 |
+
catch (e) { try { const ov = document.getElementById("holo-login"); if (ov) ov.remove(); } catch {} console.warn("[login] gate unavailable, booting device-stable identity:", e); }
|
| 193 |
+
// Pass the PROMISE, not an awaited value: boot warm-paints now and blocks for identity only where it must.
|
| 194 |
+
boot(root, authP).catch((e) => {
|
| 195 |
+
document.body.innerHTML = "<pre style='color:#f88;padding:16px;white-space:pre-wrap'>boot error: " + (e && e.stack || e) + "</pre>";
|
| 196 |
+
});
|
| 197 |
+
})();
|
| 198 |
+
|
| 199 |
+
// Intelligent Inbox (holo-mail) - additive, OFF by default. Enable per-operator: localStorage["holo.mail.enabled"]="1".
|
| 200 |
+
import("/apps/holo-messenger/mail/holo-mail-attach.mjs").catch(() => {});
|
| 201 |
+
// HoloChat, woven into THIS messenger — one canonical surface. Enable: localStorage["holo.chat.enabled"]="1" or ?chat=1.
|
| 202 |
+
import("/apps/holo-messenger/holo-messenger-weave.mjs").catch(() => {});
|
| 203 |
+
// Colour re-tone picker — colours only, all features intact. Gated: ?skins=1 or localStorage["holo.skins"]="1".
|
| 204 |
+
import("/apps/holo-messenger/messenger-skin.mjs?v=8d34a1e9297f").catch(() => {});
|
| 205 |
+
// Asset cache (service worker) — repeat opens serve the shell NETWORK-FREE. Escape hatch: open with ?nosw=1.
|
| 206 |
+
import("/apps/holo-messenger/messenger-sw-register.mjs").catch(() => {});
|
| 207 |
+
</script>
|
| 208 |
+
</body></html>
|
b/716223384b22303f78bbd63c947ab539dc9a2fd796104d9a2192b063df89b00c
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-messenger-login.mjs — thin adapter: the messenger mounts the ONE sovereign sign-in primitive.
|
| 2 |
+
//
|
| 3 |
+
// The whole sign-in stack — TEE ceremony · sovereign κ · SSO · guest · roam/recovery · Deep Resume · warm
|
| 4 |
+
// paint — now lives ONCE in /usr/lib/holo/holo-signin.mjs, so every holospace (and, next, the OS greeter)
|
| 5 |
+
// inherits it from a single call. This file supplies only the messenger's specifics: its session label, the
|
| 6 |
+
// shell it opens, and a warm-paint thunk that pulls the messenger's M1 shell bytes during the biometric tap.
|
| 7 |
+
|
| 8 |
+
import { signIn } from "/usr/lib/holo/holo-signin.mjs";
|
| 9 |
+
|
| 10 |
+
// warm the messenger shell (M1 SHELL_MANIFEST) during the human's look-and-tap, so post-auth boot is cache-hot.
|
| 11 |
+
function warmMessengerShell() {
|
| 12 |
+
try {
|
| 13 |
+
import("/apps/holo-messenger/holo-m1-boot.mjs")
|
| 14 |
+
.then((m) => { for (const p of (m.SHELL_MANIFEST || [])) { try { fetch(p, { cache: "force-cache" }).catch(() => {}); } catch {} } })
|
| 15 |
+
.catch(() => {});
|
| 16 |
+
} catch {}
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
// mountLogin({ root, params }) — unchanged contract for app.html: resolves { principal, operator, secret }.
|
| 20 |
+
export const mountLogin = (opts = {}) => signIn({
|
| 21 |
+
app: "messenger", appName: "Holo Messenger", nextPath: "/apps/holo-messenger/app.html",
|
| 22 |
+
warmPaint: warmMessengerShell, ...opts,
|
| 23 |
+
});
|
| 24 |
+
export default mountLogin;
|
b/7247a61220c39a750bc27806475dd592f15e763f3b128c54e2c0580039aec673
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// messenger-sw-register.mjs — eagerly register the ONE messenger service worker (holo-sw.js: Reach push +
|
| 2 |
+
// M1 shell cache) on boot, so repeat opens are network-free even before push permission is granted. Additive
|
| 3 |
+
// + catch-guarded so it can never block boot. Escape hatch: open with ?nosw=1 to unregister it.
|
| 4 |
+
//
|
| 5 |
+
// holo-sw.js is ALSO registered by the Reach push flow (holo-push.mjs) — same script, same {type:module}, same
|
| 6 |
+
// dir scope, so the two calls collapse to one registration (idempotent). We register here too because caching
|
| 7 |
+
// must not wait on push. We also retire any stale competing worker (the old messenger-sw.js) at this scope.
|
| 8 |
+
|
| 9 |
+
(async () => {
|
| 10 |
+
if (!("serviceWorker" in navigator)) return;
|
| 11 |
+
try {
|
| 12 |
+
const u = new URL(location.href);
|
| 13 |
+
// DEV ITERATION: on the dev-serve port (8472) OR with ?nosw=1, never let a worker cache the shell — it makes
|
| 14 |
+
// edits appear "still old" until a hard-refresh. Unregister any worker at this scope AND drop its caches so the
|
| 15 |
+
// next plain reload is always the live build. The M1 shell-cache stays intact on every other origin/port
|
| 16 |
+
// (guest :8493, host, holo://), so `holo m1-verify` and production instant-boot are unaffected.
|
| 17 |
+
const devIterate = location.port === "8472" || location.port === "8474" || u.searchParams.get("nosw") === "1";
|
| 18 |
+
if (devIterate) {
|
| 19 |
+
for (const r of await navigator.serviceWorker.getRegistrations()) {
|
| 20 |
+
if ((r.scope || "").includes("/apps/holo-messenger/")) await r.unregister();
|
| 21 |
+
}
|
| 22 |
+
try { if (window.caches) for (const k of await caches.keys()) if (k.startsWith("holo-msgr-")) await caches.delete(k); } catch {}
|
| 23 |
+
console.log("[msgr-sw] dev/nosw — worker unregistered + shell caches cleared, serving live");
|
| 24 |
+
return;
|
| 25 |
+
}
|
| 26 |
+
// retire the old competing cache-worker if a previous build left it controlling this scope.
|
| 27 |
+
try {
|
| 28 |
+
for (const r of await navigator.serviceWorker.getRegistrations()) {
|
| 29 |
+
const s = (r.active && r.active.scriptURL) || (r.installing && r.installing.scriptURL) || (r.waiting && r.waiting.scriptURL) || "";
|
| 30 |
+
if (s.includes("/messenger-sw.js")) { await r.unregister(); console.log("[msgr-sw] retired stale messenger-sw.js"); }
|
| 31 |
+
}
|
| 32 |
+
} catch {}
|
| 33 |
+
// Module-relative registration: resolves to /apps/holo-messenger/ at the OS root and to <base>/apps/holo-messenger/
|
| 34 |
+
// on a mounted static host (e.g. GitHub Pages /<repo>/) — one registrar, every mount point.
|
| 35 |
+
await navigator.serviceWorker.register(new URL("./holo-sw.js", import.meta.url), { type: "module", scope: new URL("./", import.meta.url).pathname });
|
| 36 |
+
console.log("[msgr-sw] one worker active (push + shell cache) — repeat opens serve the shell network-free");
|
| 37 |
+
// S3: verify the SIGNED release pointer off the critical path — the standing trust bar for the worker
|
| 38 |
+
// (what an #m1 link used to grant, now on every open). Fail-soft here; fail-closed inside the module.
|
| 39 |
+
try { await import("./holo-release-boot.mjs"); } catch {}
|
| 40 |
+
} catch (e) { /* never block boot */ }
|
| 41 |
+
})();
|
b/7d883ae6cf3e4495a3377e83d4cedef8f4141c15cf8166884e0efad2592f15e9
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* messenger-skins.css — OPTIONAL colour re-tones, applied ONLY when <html> carries data-skin="…".
|
| 2 |
+
* Default (no attribute) is untouched, so this can never disturb the shipping look. Colours only — no
|
| 3 |
+
* layout — safe and fully reversible.
|
| 4 |
+
*
|
| 5 |
+
* The theme drives ~half its colour from --wa-* vars on .holo-wa-root and hardcodes the other half to the
|
| 6 |
+
* same values. This shared block re-points the major hardcoded surfaces back onto the vars; each skin then
|
| 7 |
+
* only remaps the vars and the whole surface re-tones consistently. */
|
| 8 |
+
|
| 9 |
+
html[data-skin] .holo-wa-root { background: var(--wa-bg) !important; color: var(--wa-ink); }
|
| 10 |
+
html[data-skin] .holo-wa-root .cs-main-container,
|
| 11 |
+
html[data-skin] .holo-wa-root .cs-chat-container,
|
| 12 |
+
html[data-skin] .holo-wa-root .cs-message-list { background: transparent; }
|
| 13 |
+
html[data-skin] .holo-wa-root .cs-sidebar,
|
| 14 |
+
html[data-skin] .holo-wa-root .holo-rail,
|
| 15 |
+
html[data-skin] .holo-wa-root .cs-conversation-list { background: var(--wa-side) !important; }
|
| 16 |
+
html[data-skin] .holo-wa-root .cs-conversation-header,
|
| 17 |
+
html[data-skin] .holo-wa-root .holo-list-title,
|
| 18 |
+
html[data-skin] .holo-wa-root .cs-message-input,
|
| 19 |
+
html[data-skin] .holo-wa-root .holo-composer { background: var(--wa-head) !important; }
|
| 20 |
+
html[data-skin] .holo-wa-root .cs-search,
|
| 21 |
+
html[data-skin] .holo-wa-root .holo-wp-search,
|
| 22 |
+
html[data-skin] .holo-wa-root .cs-message-input__content-editor-wrapper,
|
| 23 |
+
html[data-skin] .holo-wa-root .cs-message-input__content-editor { background: color-mix(in srgb, var(--wa-head) 78%, var(--wa-bg)) !important; color: var(--wa-ink) !important; }
|
| 24 |
+
html[data-skin] .holo-wa-root .cs-conversation:hover,
|
| 25 |
+
html[data-skin] .holo-wa-root .cs-conversation.cs-conversation--active { background: var(--wa-head) !important; }
|
| 26 |
+
html[data-skin] .holo-wa-root .cs-conversation__name,
|
| 27 |
+
html[data-skin] .holo-wa-root .cs-conversation-header__user-name { color: var(--wa-ink) !important; }
|
| 28 |
+
html[data-skin] .holo-wa-root .cs-conversation__info,
|
| 29 |
+
html[data-skin] .holo-wa-root .cs-conversation__last-sender,
|
| 30 |
+
html[data-skin] .holo-wa-root .cs-conversation-header__info { color: var(--wa-dim) !important; }
|
| 31 |
+
html[data-skin] .holo-wa-root .cs-message--incoming .cs-message__content { background: var(--wa-in) !important; color: var(--wa-ink) !important; }
|
| 32 |
+
html[data-skin] .holo-wa-root .cs-message--outgoing .cs-message__content { background: var(--wa-out) !important; }
|
| 33 |
+
html[data-skin] .holo-wa-root .cs-message__content { color: var(--wa-ink); }
|
| 34 |
+
html[data-skin] .holo-wa-root .holo-rail-btn.on,
|
| 35 |
+
html[data-skin] .holo-wa-root .holo-rail-net.on,
|
| 36 |
+
html[data-skin] .holo-wa-root .holo-catchup-pill { color: var(--wa-accent) !important; }
|
| 37 |
+
html[data-skin] .holo-wa-root .holo-c-send,
|
| 38 |
+
html[data-skin] .holo-wa-root .cs-button--send { background: var(--wa-accent) !important; }
|
| 39 |
+
|
| 40 |
+
/* ── cooler / graphite ── */
|
| 41 |
+
html[data-skin="graphite"] .holo-wa-root { --wa-bg:#0d1117; --wa-side:#12171e; --wa-head:#1c232c; --wa-in:#1c232c; --wa-out:#1f4a55; --wa-ink:#e6eaf0; --wa-dim:#8b96a3; --wa-accent:#4ab3d4; --wa-line:#222a34; }
|
| 42 |
+
/* ── warmer ── */
|
| 43 |
+
html[data-skin="warm"] .holo-wa-root { --wa-bg:#14100c; --wa-side:#1b1611; --wa-head:#271f18; --wa-in:#271f18; --wa-out:#4d3f1f; --wa-ink:#efe8df; --wa-dim:#a89a88; --wa-accent:#e0a53c; --wa-line:#2c241b; }
|
| 44 |
+
/* ── higher contrast ── */
|
| 45 |
+
html[data-skin="contrast"] .holo-wa-root { --wa-bg:#000000; --wa-side:#0a0a0a; --wa-head:#181818; --wa-in:#1d1d1d; --wa-out:#00805e; --wa-ink:#ffffff; --wa-dim:#a6adb4; --wa-accent:#00e08a; --wa-line:#2a2a2a; }
|
| 46 |
+
/* ── light mode (experimental: some deep-hardcoded text may stay dark) ── */
|
| 47 |
+
html[data-skin="light"] .holo-wa-root { --wa-bg:#d7dce1; --wa-side:#ffffff; --wa-head:#f0f2f5; --wa-in:#ffffff; --wa-out:#d9fdd3; --wa-ink:#111b21; --wa-dim:#54656f; --wa-accent:#008069; --wa-line:#e4e8ec; }
|
| 48 |
+
html[data-skin="light"] .holo-wa-root .cs-message__content { color: #111b21 !important; }
|
| 49 |
+
html[data-skin="light"] .holo-wa-root .cs-message-list { background: var(--wa-bg) !important; background-image: none !important; }
|
| 50 |
+
html[data-skin="light"] .holo-wa-root .holo-wp-tile { background-image: none !important; }
|
| 51 |
+
|
| 52 |
+
/* ────────────────────────────────────────────────────────────────────────────────────────────────
|
| 53 |
+
* HI-DPI + HIGH-FPS layer (always on, colour-independent). The honest "8K/retina" story: a DOM/CSS UI
|
| 54 |
+
* is ALREADY resolution-independent — text and the SVG icons are vectors, so on a retina/4K/8K panel it
|
| 55 |
+
* renders at the display's native device pixels with zero extra work (no bitmaps to upscale). What we
|
| 56 |
+
* actually tune here is smoothness + latency:
|
| 57 |
+
* • content-visibility skips rendering/layout of off-screen chat rows → a huge list scrolls at the
|
| 58 |
+
* display's full refresh (60/120Hz) and first paint is near-instant (browser does O(visible) work).
|
| 59 |
+
* • the scrollers are promoted to their own GPU layer so scrolling never touches the main thread.
|
| 60 |
+
* • crisp text rasterisation + high-quality image scaling for hi-DPI. */
|
| 61 |
+
.holo-wa-root { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-rendering: optimizeLegibility; }
|
| 62 |
+
.holo-wa-root .cs-conversation { content-visibility: auto; contain-intrinsic-size: auto 72px; }
|
| 63 |
+
.holo-wa-root .cs-message { content-visibility: auto; contain-intrinsic-size: auto 44px; }
|
| 64 |
+
.holo-wa-root .cs-conversation-list,
|
| 65 |
+
.holo-wa-root .cs-message-list .scrollbar-container,
|
| 66 |
+
.holo-wa-root .cs-message-list__scroll-wrapper { will-change: scroll-position; transform: translateZ(0); backface-visibility: hidden; overflow-anchor: auto; }
|
| 67 |
+
.holo-wa-root .cs-avatar img, .holo-wa-root .holo-av-real, .holo-wa-root .holo-media-el img { image-rendering: auto; }
|
| 68 |
+
.holo-wa-root .holo-media-el img, .holo-wa-root .cs-message img { image-rendering: -webkit-optimize-contrast; }
|
| 69 |
+
@media (min-resolution: 1.5dppx) { .holo-wa-root { text-rendering: geometricPrecision; } }
|
| 70 |
+
|
| 71 |
+
/* floating skin picker */
|
| 72 |
+
.holo-skin-pick { position: fixed; right: 16px; bottom: 86px; z-index: 90; display: flex; gap: 4px; align-items: center; background: #0b141aee; border: 1px solid #2a3942; border-radius: 999px; padding: 5px; backdrop-filter: blur(10px); font: 12.5px ui-sans-serif, system-ui, sans-serif; box-shadow: 0 8px 30px #0007; }
|
| 73 |
+
.holo-skin-pick button { background: transparent; border: 0; color: #aebac1; border-radius: 999px; padding: 5px 11px; cursor: pointer; }
|
| 74 |
+
.holo-skin-pick button:hover { background: #ffffff14; color: #e9edef; }
|
| 75 |
+
.holo-skin-pick button.on { background: #00a884; color: #0b141a; font-weight: 600; }
|
| 76 |
+
|
| 77 |
+
/* instant wallpaper */
|
| 78 |
+
/* An inline low-res earth paints INSTANTLY (zero network — it ships in this CSS) as the chat-pane
|
| 79 |
+
* backdrop; the full 2560px wallpaper is preloaded (see app.html) and covers it the moment it decodes.
|
| 80 |
+
* So the pane is never empty and there is no visible load. */
|
| 81 |
+
html[data-skin] .holo-wa-root .cs-chat-container { background: var(--wa-bg) url("data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/2wBDABQODxIPDRQSERIXFhQYHzMhHxwcHz8tLyUzSkFOTUlBSEZSXHZkUldvWEZIZoxob3p9hIWET2ORm4+AmnaBhH//2wBDARYXFx8bHzwhITx/VEhUf39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f39/f3//wAARCAAgADADASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwCAR+1SLHVaPUYm2Zyp/jyOB9KE1AbzgjPXaRgAf41pdisjQSL2/SphDx0/Sq8epW64zknuBV6O+s3XKzJ9CcGk5tFciZGIT6fpT1h+YZx+VTC5tuomT880yTUdNgI8ycF89EXJqPavoP2aW5ya28f8bMT9cVIsVqDyCfq1VPtPIOwgfWpDcKFyvzH8q1ujKzL8bWynAiT8RmpGmTsiAfSs0XR/5aKOem3mnrdkcBGz+FF0Go6aW6ZiEwF/6ZjH61XNvdH/AJZnn3qc3rFdrQMwqP7bcKP3cSj2C0tB3Z//2Q==") center center / cover no-repeat !important; }
|
| 82 |
+
html[data-skin] .holo-wa-root .cs-message-list { transition: background-image .25s ease; }
|
| 83 |
+
|
| 84 |
+
/* ────────────────────────────────────────────────────────────────────────────────────────────────
|
| 85 |
+
* SLACK skin — data-skin="slack". Unlike the colour-only re-tones above, this one also does LAYOUT: the
|
| 86 |
+
* Slack "window envelope". An aubergine chrome (far-left rail + channel sidebar) wraps a content pane that
|
| 87 |
+
* floats as a rounded island inset by an EVEN margin on ALL FOUR sides — the darker chrome shows through
|
| 88 |
+
* every gap (top, right, bottom, AND a left gutter between sidebar and pane), with rounded corners + a soft
|
| 89 |
+
* shadow for depth. Placed at end-of-file so source order beats the base skin block AND the wallpaper rule
|
| 90 |
+
* above (equal specificity → later wins); every rule carries `.holo-wa-root` to match the base depth.
|
| 91 |
+
*
|
| 92 |
+
* Two coupling facts make the float robust: (1) the composer is a position:absolute overlay on .holo-main
|
| 93 |
+
* whose `left` is CSS `34%` — the SAME 34% as the sidebar's flex-basis — so re-anchoring it to
|
| 94 |
+
* `calc(34% + gap)` keeps it glued to the gapped pane at every window width, no JS. (2) the bundle gives the
|
| 95 |
+
* pane height:100%, which ignores margin and overflows; `height:auto` + flex:1 1 auto lets the even margin
|
| 96 |
+
* actually shrink it. ALL geometry lives in the ≥900px media query so the narrow/mobile single-column layout
|
| 97 |
+
* (sidebar collapses, composer full-bleed) is left exactly as the bundle intends. Colours + margins only,
|
| 98 |
+
* fully reversible, every feature intact. */
|
| 99 |
+
html[data-skin="slack"] .holo-wa-root {
|
| 100 |
+
--wa-bg:var(--holo-chrome, #150E1A); --wa-side:#2A1530; --wa-head:#1B1B1F; --wa-in:#26262B; --wa-out:#3A2140;
|
| 101 |
+
--wa-ink:#E9E5ED; --wa-dim:#A99DB4; --wa-accent:#C6A6E6; --wa-line:#ffffff12;
|
| 102 |
+
--slk-pane:#1B1B1F; --slk-hi:#5C2D66; --slk-hover:#3A2140; --slk-gap:var(--holo-env-gap, 10px);
|
| 103 |
+
background: var(--wa-bg) !important;
|
| 104 |
+
}
|
| 105 |
+
/* chrome behind the floating pane (the colour seen in the inset margin/gutter) */
|
| 106 |
+
html[data-skin="slack"] .holo-wa-root.holo-wa-root,
|
| 107 |
+
html[data-skin="slack"] .holo-wa-root .cs-main-container { background: var(--wa-bg) !important; }
|
| 108 |
+
/* the WebGPU wallpaper backdrop is off in Slack — the pane is a flat surface */
|
| 109 |
+
html[data-skin="slack"] .holo-wa-root .holo-backdrop { display: none !important; }
|
| 110 |
+
/* far-left workspace rail: flush with the chrome — transparent over the same --wa-bg ground as the menu, no seam,
|
| 111 |
+
so rail + list + gutter are ONE continuous background and only the conversation floats (the Slack move). */
|
| 112 |
+
html[data-skin="slack"] .holo-wa-root .holo-rail { background:var(--holo-chrome, #150E1A) !important; border-right:0 !important; }
|
| 113 |
+
html[data-skin="slack"] .holo-wa-root .holo-rail-btn.on,
|
| 114 |
+
html[data-skin="slack"] .holo-wa-root .holo-rail-net.on { color: var(--wa-accent) !important; }
|
| 115 |
+
/* THE LEFT MENU IS THE BACKGROUND — like Slack, the rail, the channel list and the gutter around the floating
|
| 116 |
+
pane are ONE continuous chrome (the --wa-bg ground), not a separate lighter panel. So every sidebar surface is
|
| 117 |
+
transparent: the menu items sit directly on the background, and the ONLY thing that floats is the conversation
|
| 118 |
+
island. This is what makes the whole shell read as one window with content lifted out of it. */
|
| 119 |
+
html[data-skin="slack"] .holo-wa-root .cs-sidebar.cs-sidebar--left,
|
| 120 |
+
html[data-skin="slack"] .holo-wa-root .cs-conversation-list,
|
| 121 |
+
html[data-skin="slack"] .holo-wa-root .holo-list-head,
|
| 122 |
+
html[data-skin="slack"] .holo-wa-root .holo-list-title,
|
| 123 |
+
html[data-skin="slack"] .holo-wa-root .holo-chips,
|
| 124 |
+
html[data-skin="slack"] .holo-wa-root .holo-catchup { background: transparent !important; }
|
| 125 |
+
html[data-skin="slack"] .holo-wa-root .cs-search { background:#1F0F24 !important; border:1px solid #ffffff12 !important; color:var(--wa-ink) !important; }
|
| 126 |
+
/* conversation rows = inset rounded pills (Slack). Inset applied to EVERY row so hover/active only swap the
|
| 127 |
+
* background — no layout jump. Active carries the highlight + white text. */
|
| 128 |
+
html[data-skin="slack"] .holo-wa-root .cs-conversation { margin:1px 8px !important; border-radius:8px !important; }
|
| 129 |
+
html[data-skin="slack"] .holo-wa-root .cs-conversation:hover { background: var(--slk-hover) !important; }
|
| 130 |
+
html[data-skin="slack"] .holo-wa-root .cs-conversation.cs-conversation--active { background: var(--slk-hi) !important; color:#fff !important; }
|
| 131 |
+
html[data-skin="slack"] .holo-wa-root .cs-conversation.cs-conversation--active * { color:#fff !important; }
|
| 132 |
+
/* pane + its inner surfaces (colours are width-independent; the float geometry is gated below) */
|
| 133 |
+
html[data-skin="slack"] .holo-wa-root .cs-chat-container { background: var(--slk-pane) !important; }
|
| 134 |
+
html[data-skin="slack"] .holo-wa-root .cs-message-list { background: var(--slk-pane) !important; background-image: none !important; }
|
| 135 |
+
html[data-skin="slack"] .holo-wa-root .cs-conversation-header { background: var(--slk-pane) !important; border-bottom: 1px solid #ffffff12; }
|
| 136 |
+
html[data-skin="slack"] .holo-wa-root .holo-composer { background: var(--slk-pane) !important; border-top: 1px solid #ffffff12; }
|
| 137 |
+
html[data-skin="slack"] .holo-wa-root .holo-c-input { background:#26262B !important; border:1px solid #ffffff14 !important; border-radius:8px !important; color:var(--wa-ink) !important; }
|
| 138 |
+
|
| 139 |
+
/* ── the floating envelope (desktop only; narrow layout keeps the bundle's single-column defaults) ── */
|
| 140 |
+
@media (min-width: 900px) {
|
| 141 |
+
html[data-skin="slack"] .holo-wa-root .cs-chat-container {
|
| 142 |
+
flex: 1 1 auto !important; min-width: 0; height: auto !important; align-self: stretch;
|
| 143 |
+
margin: var(--slk-gap) !important; /* even inset on all four sides */
|
| 144 |
+
border-radius: 12px; overflow: hidden;
|
| 145 |
+
box-shadow: 0 0 0 1px #00000055, 0 14px 38px #00000085;
|
| 146 |
+
}
|
| 147 |
+
/* glue the composer to the gapped pane: left tracks the sidebar's 34%, plus the gutter */
|
| 148 |
+
html[data-skin="slack"] .holo-wa-root .holo-composer {
|
| 149 |
+
left: calc(34% + var(--slk-gap)) !important; right: var(--slk-gap) !important;
|
| 150 |
+
bottom: var(--slk-gap) !important; width: auto !important; border-radius: 0 0 12px 12px;
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
/* ── THEATRE MODE — full-screen wallpaper + glass, under the Slack skin ────────────────────────────
|
| 155 |
+
* Theatre (`.theater`, toggle ⌘/Ctrl+\) collapses the rail + sidebar and floats ONE conversation over a
|
| 156 |
+
* FULL-BLEED wallpaper as centred glass capsules (see whatsapp-theme.css "Stage 6"). Three things make it
|
| 157 |
+
* immersive + fast here:
|
| 158 |
+
* 1. WALLPAPER edge-to-edge — a static image on the root (the whole viewport), NOT the WebGPU `.holo-backdrop`
|
| 159 |
+
* canvas (which we turn OFF: it was rendering at opacity:0, all cost no pixels). The image is the one
|
| 160 |
+
* app.html already preloads, so it paints with zero extra latency; every layer above it is transparent.
|
| 161 |
+
* 2. LIGHT + CLEAR — the heavy radial vignette + hue-tint is replaced by a soft top/bottom legibility scrim
|
| 162 |
+
* only, so the middle of the wallpaper stays bright and open.
|
| 163 |
+
* 3. STUNNING GLASS, CHEAP — real backdrop-blur is limited to the TWO capsules (header + composer); message
|
| 164 |
+
* bubbles are plain translucent fills (no per-bubble blur) so a long thread never janks input.
|
| 165 |
+
* Selector depth `…"slack"] .holo-wa-root.theater .X` = (0,4,1) beats the base slack rules (0,3,1) and
|
| 166 |
+
* theatre's own (0,3,0); transforms are left untouched so theatre's centring + auto-quiet fade keep animating. */
|
| 167 |
+
html[data-skin="slack"] .holo-wa-root.theater {
|
| 168 |
+
/* FOLLOW the operator's chosen wallpaper (--wp-img, set from localStorage holo-messenger/wallpaper-src),
|
| 169 |
+
falling back to the preloaded default when unset. `none` (the "plain" mode) is honoured → dark. */
|
| 170 |
+
background: #0b0f14 var(--wp-img, url("/apps/holo-messenger/_vendor/wallpaper-default.jpg")) center center / cover no-repeat !important;
|
| 171 |
+
}
|
| 172 |
+
html[data-skin="slack"] .holo-wa-root.theater .holo-backdrop { display: none !important; } /* WebGPU canvas off → lower latency */
|
| 173 |
+
html[data-skin="slack"] .holo-wa-root.theater .cs-chat-container {
|
| 174 |
+
background: transparent !important; margin: 0 !important; border-radius: 0 !important;
|
| 175 |
+
overflow: visible !important; box-shadow: none !important; /* full-bleed, no slack box */
|
| 176 |
+
}
|
| 177 |
+
/* transparent reading column, aligned to the SAME 812px as the chrome so bubbles line up with the composer */
|
| 178 |
+
html[data-skin="slack"] .holo-wa-root.theater .cs-message-list {
|
| 179 |
+
background: transparent !important;
|
| 180 |
+
padding-left: max(20px, calc((100% - 812px) / 2)) !important;
|
| 181 |
+
padding-right: max(20px, calc((100% - 812px) / 2)) !important;
|
| 182 |
+
}
|
| 183 |
+
/* clear + light: only a soft top/bottom scrim for legibility; the wallpaper stays bright in the middle */
|
| 184 |
+
html[data-skin="slack"] .holo-wa-root.theater .holo-main::before {
|
| 185 |
+
background: linear-gradient(to bottom, rgba(0,0,0,.30) 0%, transparent 16%, transparent 80%, rgba(0,0,0,.42) 100%) !important;
|
| 186 |
+
}
|
| 187 |
+
/* stunning glass capsules — vivid backdrop-blur, crisp edge, top highlight (only these two get real blur) */
|
| 188 |
+
html[data-skin="slack"] .holo-wa-root.theater .cs-conversation-header,
|
| 189 |
+
html[data-skin="slack"] .holo-wa-root.theater .holo-composer {
|
| 190 |
+
background: rgba(22,24,32,.44) !important;
|
| 191 |
+
-webkit-backdrop-filter: blur(30px) saturate(1.4) brightness(1.06) !important;
|
| 192 |
+
backdrop-filter: blur(30px) saturate(1.4) brightness(1.06) !important;
|
| 193 |
+
border: 1px solid rgba(255,255,255,.16) !important;
|
| 194 |
+
box-shadow: 0 14px 40px rgba(0,0,0,.42), inset 0 1px 0 rgba(255,255,255,.16) !important;
|
| 195 |
+
}
|
| 196 |
+
html[data-skin="slack"] .holo-wa-root.theater .holo-composer {
|
| 197 |
+
left: 50% !important; right: auto !important; width: min(calc(100% - 40px), 812px) !important;
|
| 198 |
+
bottom: 18px !important; border-radius: 18px !important;
|
| 199 |
+
}
|
| 200 |
+
/* bubbles: light translucent fills (no blur = no jank on long threads) */
|
| 201 |
+
html[data-skin="slack"] .holo-wa-root.theater .cs-message--incoming .cs-message__content { background: rgba(28,28,36,.62) !important; }
|
| 202 |
+
html[data-skin="slack"] .holo-wa-root.theater .cs-message--outgoing .cs-message__content { background: rgba(58,33,64,.60) !important; }
|
| 203 |
+
|
| 204 |
+
/* ── FRAME-COLLAPSED — the immersive focus view (chats folded into the rail) ───────────────────────
|
| 205 |
+
* "Collapse chats into the rail" (`.holo-main.frame-collapsed`) folds the conversation list away and lets
|
| 206 |
+
* ONE conversation take the whole frame. The slack skin otherwise left it broken: the composer stayed pinned
|
| 207 |
+
* to `left:34%` (the vanished sidebar edge) so it floated stranded in the middle. Here we make it the SAME
|
| 208 |
+
* immersive treatment as theatre — full-screen wallpaper + glass capsules — but KEEP the rail as a clean
|
| 209 |
+
* glass nav (that's the whole point of this mode), and centre the header/composer/reading-column in the frame
|
| 210 |
+
* to the RIGHT of the rail so everything lines up.
|
| 211 |
+
* · `:has(.holo-main.frame-collapsed)` puts the wallpaper on the ROOT (so it sits behind the rail too).
|
| 212 |
+
* · `:not(.theater)` so theatre (which hides the rail) keeps full ownership when both happen to be on.
|
| 213 |
+
* · Only 3 blur layers total (rail + header + composer); bubbles are plain translucent — stays responsive. */
|
| 214 |
+
html[data-skin="slack"] .holo-wa-root:has(.holo-main.frame-collapsed):not(.theater) {
|
| 215 |
+
/* same as theatre: follow the operator's --wp-img, fall back to the preloaded default */
|
| 216 |
+
background: #0b0f14 var(--wp-img, url("/apps/holo-messenger/_vendor/wallpaper-default.jpg")) center center / cover no-repeat !important;
|
| 217 |
+
}
|
| 218 |
+
html[data-skin="slack"] .holo-wa-root:has(.holo-main.frame-collapsed):not(.theater) .holo-backdrop { display: none !important; }
|
| 219 |
+
/* the left nav bar stays — a clean SOLID strip; the wallpaper covers the entire screen EXCEPT this rail */
|
| 220 |
+
html[data-skin="slack"] .holo-wa-root:has(.holo-main.frame-collapsed):not(.theater) .holo-rail {
|
| 221 |
+
background: #17111D !important;
|
| 222 |
+
border-right: 1px solid rgba(255,255,255,.08) !important;
|
| 223 |
+
}
|
| 224 |
+
/* transparent full-bleed frame + reading column centred in the space right of the rail */
|
| 225 |
+
html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-chat-container {
|
| 226 |
+
background: transparent !important; margin: 0 !important; border-radius: 0 !important; overflow: visible !important; box-shadow: none !important;
|
| 227 |
+
}
|
| 228 |
+
html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-message-list {
|
| 229 |
+
background: transparent !important;
|
| 230 |
+
padding-left: max(20px, calc((100% - 812px) / 2)) !important;
|
| 231 |
+
padding-right: max(20px, calc((100% - 812px) / 2)) !important;
|
| 232 |
+
}
|
| 233 |
+
html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed::before {
|
| 234 |
+
content: ""; position: absolute; inset: 0; pointer-events: none; z-index: 0;
|
| 235 |
+
background: linear-gradient(to bottom, rgba(0,0,0,.30) 0%, transparent 16%, transparent 80%, rgba(0,0,0,.42) 100%);
|
| 236 |
+
}
|
| 237 |
+
/* glass header + composer, centred in the frame (the rail is the nav, the frame is everything right of it) */
|
| 238 |
+
html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-conversation-header,
|
| 239 |
+
html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .holo-composer {
|
| 240 |
+
background: rgba(22,24,32,.44) !important;
|
| 241 |
+
-webkit-backdrop-filter: blur(30px) saturate(1.4) brightness(1.06) !important;
|
| 242 |
+
backdrop-filter: blur(30px) saturate(1.4) brightness(1.06) !important;
|
| 243 |
+
border: 1px solid rgba(255,255,255,.16) !important;
|
| 244 |
+
box-shadow: 0 14px 40px rgba(0,0,0,.42), inset 0 1px 0 rgba(255,255,255,.16) !important;
|
| 245 |
+
}
|
| 246 |
+
html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-conversation-header {
|
| 247 |
+
margin: 14px auto 0 !important; max-width: 812px !important; width: calc(100% - 40px) !important; border-radius: 16px !important;
|
| 248 |
+
}
|
| 249 |
+
html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .holo-composer {
|
| 250 |
+
left: 50% !important; right: auto !important; transform: translateX(-50%) !important;
|
| 251 |
+
width: min(calc(100% - 40px), 812px) !important; bottom: 18px !important; border-radius: 18px !important;
|
| 252 |
+
}
|
| 253 |
+
html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-message--incoming .cs-message__content { background: rgba(28,28,36,.62) !important; }
|
| 254 |
+
html[data-skin="slack"] .holo-wa-root:not(.theater) .holo-main.frame-collapsed .cs-message--outgoing .cs-message__content { background: rgba(58,33,64,.60) !important; }
|
| 255 |
+
|
| 256 |
+
/* ── BASE-LAYER fix: composer alignment in frame-collapsed, for ANY skin ───────────────────────────
|
| 257 |
+
* Skin-agnostic (this file is loaded regardless of the active skin). The bundle pins the composer to
|
| 258 |
+
* `left:34%` — the sidebar's flex-basis — but frame-collapsed folds the sidebar to 0, so on every NON-slack
|
| 259 |
+
* skin (graphite/warm/contrast/light/none) the composer floated stranded in the middle. Align it to the
|
| 260 |
+
* full-bleed chat (left:0 = the rail's right edge; right:0 is already the base). `:not(.theater)` yields to
|
| 261 |
+
* theatre's own centred composer; slack's higher-specificity immersive rule above wins for the slack skin. */
|
| 262 |
+
.holo-wa-root:not(.theater) .holo-main.frame-collapsed .holo-composer { left: 0 !important; }
|
b/86a3f68c5070d7a543e52768811268d22f2051a5033c11d5281ecd2a36c2dd44
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-release-boot.mjs — S3 of the downstream-holospace initiative: EVERY boot verifies through the
|
| 2 |
+
// SIGNED release pointer, not just #m1-link opens.
|
| 3 |
+
//
|
| 4 |
+
// Today the worker's integrity bar is seeded from shell-manifest.json — unsigned bytes from the origin —
|
| 5 |
+
// and only an #m1 boot link upgrades it to link-verified. This module makes the SIGNED strand head the
|
| 6 |
+
// standing bar: fetch release.json (the ONE mutable fetch, no-store) → acceptHead() against the local pin
|
| 7 |
+
// (TOFU, key-continuity, seq-monotonic — holo-release-verify.mjs) → prove the origin's manifest is the
|
| 8 |
+
// EXACT one the release signed (bytes re-derive to payload.manifest, aggregate re-derives to payload.shell)
|
| 9 |
+
// → only then pin + hand the manifest to the worker. A refused pointer changes NOTHING: the worker keeps
|
| 10 |
+
// serving the last verified shell (fail-closed to last-good, never to blank).
|
| 11 |
+
//
|
| 12 |
+
// Additive + fail-soft at the seam (a deploy without release.json is a quiet no-op), fail-CLOSED at the
|
| 13 |
+
// bar (no signature → no upgrade). Injectable core; the browser auto-run wires live fetch/storage/worker.
|
| 14 |
+
|
| 15 |
+
import { acceptHead } from "./holo-release-verify.mjs";
|
| 16 |
+
import { shellAggregate } from "./holo-m1-boot.mjs";
|
| 17 |
+
|
| 18 |
+
export const PIN_KEY = "holo.release.pin";
|
| 19 |
+
const _hex = (buf) => [...new Uint8Array(buf)].map((x) => x.toString(16).padStart(2, "0")).join("");
|
| 20 |
+
const sha256hex = async (buf) => _hex(await (globalThis.crypto).subtle.digest("SHA-256", buf));
|
| 21 |
+
|
| 22 |
+
// releaseBoot({ fetchFn, storage, post, baseUrl }) → { ok, why?, pin?, unchanged? }
|
| 23 |
+
// fetchFn — fetch-compatible; storage — localStorage-compatible {getItem,setItem};
|
| 24 |
+
// post — receives the verified manifest message for the worker; baseUrl — this module's URL (relocatable).
|
| 25 |
+
export async function releaseBoot({ fetchFn, storage, post = null, baseUrl } = {}) {
|
| 26 |
+
// 1. the pointer — the ONE mutable fetch (everything else it names is immutable, κ-addressed)
|
| 27 |
+
let head = null;
|
| 28 |
+
try { const r = await fetchFn(new URL("../../release.json", baseUrl), { cache: "no-store" }); if (r && r.ok) head = await r.json(); } catch {}
|
| 29 |
+
if (!head) return { ok: false, why: "no-pointer" }; // pre-strand deploy: additive no-op
|
| 30 |
+
// 2. accept against the durable pin (SEC-1/4: forge, tamper, replay, key-swap → refused by name)
|
| 31 |
+
let pinned = null; try { pinned = JSON.parse(storage.getItem(PIN_KEY) || "null"); } catch {}
|
| 32 |
+
const a = await acceptHead(head, pinned);
|
| 33 |
+
if (!a.ok) return { ok: false, why: a.why, pinned }; // bar unchanged — last-good keeps serving
|
| 34 |
+
// 3. the head COMMITS the shell: the origin's manifest must be the exact object the release signed
|
| 35 |
+
const p = head["holstr:payload"] || {};
|
| 36 |
+
let mfBytes = null;
|
| 37 |
+
try { const r = await fetchFn(new URL("./shell-manifest.json", baseUrl), { cache: "no-store" }); if (r && r.ok) mfBytes = await r.arrayBuffer(); } catch {}
|
| 38 |
+
if (!mfBytes) return { ok: false, why: "no-manifest", head: head.id };
|
| 39 |
+
if (p.manifest && (await sha256hex(mfBytes)) !== p.manifest) return { ok: false, why: "manifest-not-signed", head: head.id };
|
| 40 |
+
let manifest = null; try { manifest = JSON.parse(new TextDecoder().decode(mfBytes)); } catch {}
|
| 41 |
+
const agg = manifest ? await shellAggregate(manifest) : "";
|
| 42 |
+
if (!agg || agg !== p.shell) return { ok: false, why: "shell-mismatch", head: head.id };
|
| 43 |
+
// 4. accepted — advance the pin, upgrade the worker's bar to SIGNED-verified (what #m1 links did, now standing)
|
| 44 |
+
try { storage.setItem(PIN_KEY, JSON.stringify(a.pin)); } catch {}
|
| 45 |
+
try { if (post) post({ type: "holo-shell-manifest", assets: manifest.assets, runtime: manifest.runtime || [] }); } catch {}
|
| 46 |
+
return { ok: true, pin: a.pin, unchanged: !!a.unchanged, shell: p.shell };
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// rollback is an EXPLICIT LOCAL ACT (never something a remote pointer can cause): re-pin a parent entry
|
| 50 |
+
// the operator chose from the strand. The next acceptHead treats it as the baseline.
|
| 51 |
+
export function repin(storage, entry) {
|
| 52 |
+
const pin = { id: entry.id, seq: entry["holstr:seq"], pub: entry["holstr:pub"] };
|
| 53 |
+
storage.setItem(PIN_KEY, JSON.stringify(pin));
|
| 54 |
+
return pin;
|
| 55 |
+
}
|
| 56 |
+
|
| 57 |
+
// ── browser auto-run: after the worker registers, verify the pointer off the critical path ────────────
|
| 58 |
+
if (typeof window !== "undefined" && typeof navigator !== "undefined") {
|
| 59 |
+
(async () => {
|
| 60 |
+
try {
|
| 61 |
+
if (!("serviceWorker" in navigator)) return;
|
| 62 |
+
const reg = await navigator.serviceWorker.ready;
|
| 63 |
+
const r = await releaseBoot({
|
| 64 |
+
fetchFn: fetch.bind(globalThis), storage: window.localStorage, baseUrl: import.meta.url,
|
| 65 |
+
post: (m) => { try { (reg.active || navigator.serviceWorker.controller)?.postMessage(m); } catch {} },
|
| 66 |
+
});
|
| 67 |
+
window.HoloRelease = Object.assign(window.HoloRelease || {}, { last: r, repin: (e) => repin(window.localStorage, e) });
|
| 68 |
+
if (r.ok && !r.unchanged) console.log("[release] signed head pinned: seq " + r.pin.seq + " " + r.pin.id.slice(-16));
|
| 69 |
+
else if (!r.ok && r.why !== "no-pointer") console.warn("[release] pointer refused (" + r.why + ") — shell stays on the last verified release");
|
| 70 |
+
} catch {}
|
| 71 |
+
})();
|
| 72 |
+
}
|
b/8c30aa5582dffe4548d27fa7863163eba689ed65126be472c003f5884fc8e534
ADDED
|
@@ -0,0 +1,1870 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* @ts-self-types="./holospaces_web.d.ts" */
|
| 2 |
+
|
| 3 |
+
/**
|
| 4 |
+
* **The browser peer's AArch64 holospace** — a real arm64 devcontainer booted on
|
| 5 |
+
* the engine's [AArch64 core](holospaces::emulator::aarch64) (ADR-021; `CC-36` a
|
| 6 |
+
* real arm64 Linux boots, `CC-37` a stock `linux-arm64` binary runs). Its κ-disk
|
| 7 |
+
* is the **same substrate-backed `virtio-blk`** the RISC-V [`Workspace`] uses —
|
| 8 |
+
* the shared device bus (`emulator::devbus`), no per-ISA re-implementation (Law
|
| 9 |
+
* L4; `CC-7`/`CC-14`). Boot + the interactive terminal (`CC-11`) are complete;
|
| 10 |
+
* the AArch64 core's `virtio-net`/`9p` parity is the continued engine build, so
|
| 11 |
+
* this surface exposes only what the core does today — nothing it cannot fulfil.
|
| 12 |
+
*/
|
| 13 |
+
export class Aarch64Workspace {
|
| 14 |
+
static __wrap(ptr) {
|
| 15 |
+
const obj = Object.create(Aarch64Workspace.prototype);
|
| 16 |
+
obj.__wbg_ptr = ptr;
|
| 17 |
+
Aarch64WorkspaceFinalization.register(obj, obj.__wbg_ptr, obj);
|
| 18 |
+
return obj;
|
| 19 |
+
}
|
| 20 |
+
__destroy_into_raw() {
|
| 21 |
+
const ptr = this.__wbg_ptr;
|
| 22 |
+
this.__wbg_ptr = 0;
|
| 23 |
+
Aarch64WorkspaceFinalization.unregister(this);
|
| 24 |
+
return ptr;
|
| 25 |
+
}
|
| 26 |
+
free() {
|
| 27 |
+
const ptr = this.__destroy_into_raw();
|
| 28 |
+
wasm.__wbg_aarch64workspace_free(ptr, 0);
|
| 29 |
+
}
|
| 30 |
+
/**
|
| 31 |
+
* Boot a **devcontainer** on the AArch64 core: the assembled `ext4` `rootfs`
|
| 32 |
+
* (from [`DevcontainerImage::assemble_bootable`]) is taken as κ-addressed
|
| 33 |
+
* content into the shared `virtio-blk` κ-disk (`CC-7`) and mounted over
|
| 34 |
+
* `/dev/vda`; the real, unmodified arm64 Linux `kernel` boots it and runs the
|
| 35 |
+
* devcontainer's OS — entirely in the browser peer (ADR-009/011/021, `CC-37`).
|
| 36 |
+
* Drive with [`run`](Aarch64Workspace::run), streaming
|
| 37 |
+
* [`terminal_delta`](Aarch64Workspace::terminal_delta) and feeding keystrokes
|
| 38 |
+
* with [`feed_input`](Aarch64Workspace::feed_input).
|
| 39 |
+
* @param {Uint8Array} kernel
|
| 40 |
+
* @param {Uint8Array} rootfs
|
| 41 |
+
* @returns {Aarch64Workspace}
|
| 42 |
+
*/
|
| 43 |
+
static boot_devcontainer(kernel, rootfs) {
|
| 44 |
+
const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
|
| 45 |
+
const len0 = WASM_VECTOR_LEN;
|
| 46 |
+
const ptr1 = passArray8ToWasm0(rootfs, wasm.__wbindgen_malloc);
|
| 47 |
+
const len1 = WASM_VECTOR_LEN;
|
| 48 |
+
const ret = wasm.aarch64workspace_boot_devcontainer(ptr0, len0, ptr1, len1);
|
| 49 |
+
return Aarch64Workspace.__wrap(ret);
|
| 50 |
+
}
|
| 51 |
+
/**
|
| 52 |
+
* Feed raw terminal input to the running guest (`CC-11`, the AArch64 PL011
|
| 53 |
+
* analogue): the bytes become readable at the serial console, so the guest's
|
| 54 |
+
* own tty echoes, line-edits, and raises SIGINT on Ctrl-C — a real terminal.
|
| 55 |
+
* @param {Uint8Array} bytes
|
| 56 |
+
*/
|
| 57 |
+
feed_input(bytes) {
|
| 58 |
+
const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
|
| 59 |
+
const len0 = WASM_VECTOR_LEN;
|
| 60 |
+
wasm.aarch64workspace_feed_input(this.__wbg_ptr, ptr0, len0);
|
| 61 |
+
}
|
| 62 |
+
/**
|
| 63 |
+
* Whether the machine has powered off.
|
| 64 |
+
* @returns {boolean}
|
| 65 |
+
*/
|
| 66 |
+
get halted() {
|
| 67 |
+
const ret = wasm.aarch64workspace_halted(this.__wbg_ptr);
|
| 68 |
+
return ret !== 0;
|
| 69 |
+
}
|
| 70 |
+
/**
|
| 71 |
+
* Advance the running holospace by `budget` instructions (one chunk of the
|
| 72 |
+
* boot or of servicing input); returns `true` once the machine has halted.
|
| 73 |
+
* @param {number} budget
|
| 74 |
+
* @returns {boolean}
|
| 75 |
+
*/
|
| 76 |
+
run(budget) {
|
| 77 |
+
const ret = wasm.aarch64workspace_run(this.__wbg_ptr, budget);
|
| 78 |
+
return ret !== 0;
|
| 79 |
+
}
|
| 80 |
+
/**
|
| 81 |
+
* Whether the terminal has rendered `marker` yet (e.g. a ready banner).
|
| 82 |
+
* @param {string} marker
|
| 83 |
+
* @returns {boolean}
|
| 84 |
+
*/
|
| 85 |
+
shows(marker) {
|
| 86 |
+
const ptr0 = passStringToWasm0(marker, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 87 |
+
const len0 = WASM_VECTOR_LEN;
|
| 88 |
+
const ret = wasm.aarch64workspace_shows(this.__wbg_ptr, ptr0, len0);
|
| 89 |
+
return ret !== 0;
|
| 90 |
+
}
|
| 91 |
+
/**
|
| 92 |
+
* The full console the guest has produced.
|
| 93 |
+
* @returns {string}
|
| 94 |
+
*/
|
| 95 |
+
terminal() {
|
| 96 |
+
let deferred1_0;
|
| 97 |
+
let deferred1_1;
|
| 98 |
+
try {
|
| 99 |
+
const ret = wasm.aarch64workspace_terminal(this.__wbg_ptr);
|
| 100 |
+
deferred1_0 = ret[0];
|
| 101 |
+
deferred1_1 = ret[1];
|
| 102 |
+
return getStringFromWasm0(ret[0], ret[1]);
|
| 103 |
+
} finally {
|
| 104 |
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
/**
|
| 108 |
+
* The console bytes produced **since the last call** — the integrated
|
| 109 |
+
* terminal's render loop (output stays O(new bytes) per frame).
|
| 110 |
+
* @returns {Uint8Array}
|
| 111 |
+
*/
|
| 112 |
+
terminal_delta() {
|
| 113 |
+
const ret = wasm.aarch64workspace_terminal_delta(this.__wbg_ptr);
|
| 114 |
+
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 115 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 116 |
+
return v1;
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
if (Symbol.dispose) Aarch64Workspace.prototype[Symbol.dispose] = Aarch64Workspace.prototype.free;
|
| 120 |
+
|
| 121 |
+
/**
|
| 122 |
+
* The Platform Manager console, running as a browser peer that composes the
|
| 123 |
+
* substrate runtime over the interpreter `ContainerEngine`.
|
| 124 |
+
*/
|
| 125 |
+
export class Console {
|
| 126 |
+
__destroy_into_raw() {
|
| 127 |
+
const ptr = this.__wbg_ptr;
|
| 128 |
+
this.__wbg_ptr = 0;
|
| 129 |
+
ConsoleFinalization.unregister(this);
|
| 130 |
+
return ptr;
|
| 131 |
+
}
|
| 132 |
+
free() {
|
| 133 |
+
const ptr = this.__destroy_into_raw();
|
| 134 |
+
wasm.__wbg_console_free(ptr, 0);
|
| 135 |
+
}
|
| 136 |
+
/**
|
| 137 |
+
* Boot a userland holospace **in the browser**: provision it, then spawn it
|
| 138 |
+
* through the substrate runtime over the interpreter `ContainerEngine`,
|
| 139 |
+
* capture a κ snapshot of its state (suspend), resume, and terminate — the
|
| 140 |
+
* execution surface running on the browser peer (ADR-008; RT2; `CC-6`).
|
| 141 |
+
* Returns the κ-label of the suspend snapshot (state is content, Law L3).
|
| 142 |
+
* @param {Uint8Array} module
|
| 143 |
+
* @param {number} memory_bytes
|
| 144 |
+
* @returns {string}
|
| 145 |
+
*/
|
| 146 |
+
boot_userland(module, memory_bytes) {
|
| 147 |
+
let deferred3_0;
|
| 148 |
+
let deferred3_1;
|
| 149 |
+
try {
|
| 150 |
+
const ptr0 = passArray8ToWasm0(module, wasm.__wbindgen_malloc);
|
| 151 |
+
const len0 = WASM_VECTOR_LEN;
|
| 152 |
+
const ret = wasm.console_boot_userland(this.__wbg_ptr, ptr0, len0, memory_bytes);
|
| 153 |
+
var ptr2 = ret[0];
|
| 154 |
+
var len2 = ret[1];
|
| 155 |
+
if (ret[3]) {
|
| 156 |
+
ptr2 = 0; len2 = 0;
|
| 157 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 158 |
+
}
|
| 159 |
+
deferred3_0 = ptr2;
|
| 160 |
+
deferred3_1 = len2;
|
| 161 |
+
return getStringFromWasm0(ptr2, len2);
|
| 162 |
+
} finally {
|
| 163 |
+
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
| 164 |
+
}
|
| 165 |
+
}
|
| 166 |
+
/**
|
| 167 |
+
* *Control panel: configure.* Reconfigure a running instance from the panel
|
| 168 |
+
* (ADR-018; `CC-28`). `directives_json` is a JSON array of operations across
|
| 169 |
+
* the four classes, e.g. `[{"lifecycle":"suspend"}, {"forwardPort":8080},
|
| 170 |
+
* {"unforwardPort":8080}, {"network":{"fetch":true,"announce":false}},
|
| 171 |
+
* {"quota":1073741824}, {"grant":"blake3:…"}]`. The panel builds a
|
| 172 |
+
* content-addressed [`Configuration`] issued by the signed-in operator,
|
| 173 |
+
* stores it (Law L2), and returns its κ — the content the running instance
|
| 174 |
+
* resolves and applies over the substrate (no server, no RPC).
|
| 175 |
+
* @param {string} instance
|
| 176 |
+
* @param {string} directives_json
|
| 177 |
+
* @returns {string}
|
| 178 |
+
*/
|
| 179 |
+
configure(instance, directives_json) {
|
| 180 |
+
let deferred4_0;
|
| 181 |
+
let deferred4_1;
|
| 182 |
+
try {
|
| 183 |
+
const ptr0 = passStringToWasm0(instance, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 184 |
+
const len0 = WASM_VECTOR_LEN;
|
| 185 |
+
const ptr1 = passStringToWasm0(directives_json, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 186 |
+
const len1 = WASM_VECTOR_LEN;
|
| 187 |
+
const ret = wasm.console_configure(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
| 188 |
+
var ptr3 = ret[0];
|
| 189 |
+
var len3 = ret[1];
|
| 190 |
+
if (ret[3]) {
|
| 191 |
+
ptr3 = 0; len3 = 0;
|
| 192 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 193 |
+
}
|
| 194 |
+
deferred4_0 = ptr3;
|
| 195 |
+
deferred4_1 = len3;
|
| 196 |
+
return getStringFromWasm0(ptr3, len3);
|
| 197 |
+
} finally {
|
| 198 |
+
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
/**
|
| 202 |
+
* Open a fresh console — a browser peer with a local content-addressed
|
| 203 |
+
* store and the interpreter container engine.
|
| 204 |
+
*/
|
| 205 |
+
constructor() {
|
| 206 |
+
const ret = wasm.console_new();
|
| 207 |
+
this.__wbg_ptr = ret;
|
| 208 |
+
ConsoleFinalization.register(this, this.__wbg_ptr, this);
|
| 209 |
+
return this;
|
| 210 |
+
}
|
| 211 |
+
/**
|
| 212 |
+
* Provision a holospace from a `.holo` compute artifact (the *holo-file*
|
| 213 |
+
* compute form) with a memory budget, κ-addressing its parts into the
|
| 214 |
+
* peer's store (Law L2). Returns the holospace identity κ.
|
| 215 |
+
* @param {Uint8Array} code
|
| 216 |
+
* @param {number} memory_bytes
|
| 217 |
+
* @returns {string}
|
| 218 |
+
*/
|
| 219 |
+
provision(code, memory_bytes) {
|
| 220 |
+
let deferred3_0;
|
| 221 |
+
let deferred3_1;
|
| 222 |
+
try {
|
| 223 |
+
const ptr0 = passArray8ToWasm0(code, wasm.__wbindgen_malloc);
|
| 224 |
+
const len0 = WASM_VECTOR_LEN;
|
| 225 |
+
const ret = wasm.console_provision(this.__wbg_ptr, ptr0, len0, memory_bytes);
|
| 226 |
+
var ptr2 = ret[0];
|
| 227 |
+
var len2 = ret[1];
|
| 228 |
+
if (ret[3]) {
|
| 229 |
+
ptr2 = 0; len2 = 0;
|
| 230 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 231 |
+
}
|
| 232 |
+
deferred3_0 = ptr2;
|
| 233 |
+
deferred3_1 = len2;
|
| 234 |
+
return getStringFromWasm0(ptr2, len2);
|
| 235 |
+
} finally {
|
| 236 |
+
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
/**
|
| 240 |
+
* Provision a holospace from a **devcontainer** for the management console
|
| 241 |
+
* (CC-12): the `devcontainer.json` is validated against the Dev Container
|
| 242 |
+
* spec (`CC-4`) and κ-addressed into the store; the holospace's identity is
|
| 243 |
+
* the content address of its devcontainer definition (reproducible — same
|
| 244 |
+
* source ⇒ same κ, Law L1). This *provisions* (records) the holospace; the
|
| 245 |
+
* operator *enters* it to boot its OS in the workspace IDE (`CC-13`).
|
| 246 |
+
* Returns the holospace identity κ.
|
| 247 |
+
* @param {Uint8Array} config_json
|
| 248 |
+
* @param {string} arch
|
| 249 |
+
* @param {number} memory_bytes
|
| 250 |
+
* @returns {string}
|
| 251 |
+
*/
|
| 252 |
+
provision_devcontainer(config_json, arch, memory_bytes) {
|
| 253 |
+
let deferred4_0;
|
| 254 |
+
let deferred4_1;
|
| 255 |
+
try {
|
| 256 |
+
const ptr0 = passArray8ToWasm0(config_json, wasm.__wbindgen_malloc);
|
| 257 |
+
const len0 = WASM_VECTOR_LEN;
|
| 258 |
+
const ptr1 = passStringToWasm0(arch, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 259 |
+
const len1 = WASM_VECTOR_LEN;
|
| 260 |
+
const ret = wasm.console_provision_devcontainer(this.__wbg_ptr, ptr0, len0, ptr1, len1, memory_bytes);
|
| 261 |
+
var ptr3 = ret[0];
|
| 262 |
+
var len3 = ret[1];
|
| 263 |
+
if (ret[3]) {
|
| 264 |
+
ptr3 = 0; len3 = 0;
|
| 265 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 266 |
+
}
|
| 267 |
+
deferred4_0 = ptr3;
|
| 268 |
+
deferred4_1 = len3;
|
| 269 |
+
return getStringFromWasm0(ptr3, len3);
|
| 270 |
+
} finally {
|
| 271 |
+
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
/**
|
| 275 |
+
* Provision a holospace from a *Wasm-recompiled userland* (the execution
|
| 276 |
+
* surface, the second compute form — ADR-008). The module is validated
|
| 277 |
+
* against the surface contract ([`validate_userland`]) before it is
|
| 278 |
+
* κ-addressed into the store, so only a substrate-valid userland can become
|
| 279 |
+
* a holospace's code. Returns the holospace identity κ.
|
| 280 |
+
* @param {Uint8Array} module
|
| 281 |
+
* @param {number} memory_bytes
|
| 282 |
+
* @returns {string}
|
| 283 |
+
*/
|
| 284 |
+
provision_userland(module, memory_bytes) {
|
| 285 |
+
let deferred3_0;
|
| 286 |
+
let deferred3_1;
|
| 287 |
+
try {
|
| 288 |
+
const ptr0 = passArray8ToWasm0(module, wasm.__wbindgen_malloc);
|
| 289 |
+
const len0 = WASM_VECTOR_LEN;
|
| 290 |
+
const ret = wasm.console_provision_userland(this.__wbg_ptr, ptr0, len0, memory_bytes);
|
| 291 |
+
var ptr2 = ret[0];
|
| 292 |
+
var len2 = ret[1];
|
| 293 |
+
if (ret[3]) {
|
| 294 |
+
ptr2 = 0; len2 = 0;
|
| 295 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 296 |
+
}
|
| 297 |
+
deferred3_0 = ptr2;
|
| 298 |
+
deferred3_1 = len2;
|
| 299 |
+
return getStringFromWasm0(ptr2, len2);
|
| 300 |
+
} finally {
|
| 301 |
+
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
| 302 |
+
}
|
| 303 |
+
}
|
| 304 |
+
/**
|
| 305 |
+
* Resolve a holospace (or any κ) from this peer's own in-session store.
|
| 306 |
+
* Returns the bytes, or `undefined` if absent.
|
| 307 |
+
*
|
| 308 |
+
* This is a *trusted* read ([`ReadVerify::Trusted`], ADR-019): the store is
|
| 309 |
+
* the canonical memory and RAM is its cache (Law L3), so content that
|
| 310 |
+
* entered this session was already verified on the way in (on receipt, or
|
| 311 |
+
* by `put` construction). The deployed peer does not re-derive κ on every
|
| 312 |
+
* local read — that would treat its own canonical store as untrusted and is
|
| 313 |
+
* pure overhead. The re-derivation invariant still holds where untrusted
|
| 314 |
+
* bytes enter (the import/fetch boundary) and is exercised end-to-end in CI.
|
| 315 |
+
* @param {string} kappa
|
| 316 |
+
* @returns {Uint8Array | undefined}
|
| 317 |
+
*/
|
| 318 |
+
resolve(kappa) {
|
| 319 |
+
const ptr0 = passStringToWasm0(kappa, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 320 |
+
const len0 = WASM_VECTOR_LEN;
|
| 321 |
+
const ret = wasm.console_resolve(this.__wbg_ptr, ptr0, len0);
|
| 322 |
+
if (ret[3]) {
|
| 323 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 324 |
+
}
|
| 325 |
+
let v2;
|
| 326 |
+
if (ret[0] !== 0) {
|
| 327 |
+
v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 328 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 329 |
+
}
|
| 330 |
+
return v2;
|
| 331 |
+
}
|
| 332 |
+
/**
|
| 333 |
+
* The operator's roster κ — the content address that links their instances
|
| 334 |
+
* (R5). Its bytes are in the store, so another instance can resolve it.
|
| 335 |
+
* @returns {string | undefined}
|
| 336 |
+
*/
|
| 337 |
+
roster_kappa() {
|
| 338 |
+
const ret = wasm.console_roster_kappa(this.__wbg_ptr);
|
| 339 |
+
let v1;
|
| 340 |
+
if (ret[0] !== 0) {
|
| 341 |
+
v1 = getStringFromWasm0(ret[0], ret[1]).slice();
|
| 342 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 343 |
+
}
|
| 344 |
+
return v1;
|
| 345 |
+
}
|
| 346 |
+
/**
|
| 347 |
+
* Import and run a **devcontainer in the browser** — the Codespaces/Gitpod
|
| 348 |
+
* scenario without a Docker daemon or a cloud VM (arc42 chapter 1, the
|
| 349 |
+
* motivating scenario; chapter 6). The `devcontainer.json` is validated
|
| 350 |
+
* against the Dev Container spec (`CC-4`); the κ-addressed Wasm `userland`
|
| 351 |
+
* its config selects is validated against the host-ABI surface (`CC-6`) and
|
| 352 |
+
* booted through the substrate runtime over the interpreter engine — same
|
| 353 |
+
* lifecycle as a native or remote peer (Q6). Returns the suspend snapshot κ.
|
| 354 |
+
*
|
| 355 |
+
* `arch` is the operator's **architecture selection** (the Manager GUI's
|
| 356 |
+
* arch picker; ADR-021) — `"riscv64"` or `"aarch64"`. It becomes part of the
|
| 357 |
+
* holospace's content-addressed identity, so it is fixed for the holospace's
|
| 358 |
+
* lifetime (an unknown id falls back to the default RISC-V target).
|
| 359 |
+
* @param {string} repo
|
| 360 |
+
* @param {string} reference
|
| 361 |
+
* @param {string} config_path
|
| 362 |
+
* @param {Uint8Array} config_json
|
| 363 |
+
* @param {Uint8Array} userland_module
|
| 364 |
+
* @param {string} arch
|
| 365 |
+
* @param {number} memory_bytes
|
| 366 |
+
* @returns {string}
|
| 367 |
+
*/
|
| 368 |
+
run_devcontainer(repo, reference, config_path, config_json, userland_module, arch, memory_bytes) {
|
| 369 |
+
let deferred8_0;
|
| 370 |
+
let deferred8_1;
|
| 371 |
+
try {
|
| 372 |
+
const ptr0 = passStringToWasm0(repo, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 373 |
+
const len0 = WASM_VECTOR_LEN;
|
| 374 |
+
const ptr1 = passStringToWasm0(reference, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 375 |
+
const len1 = WASM_VECTOR_LEN;
|
| 376 |
+
const ptr2 = passStringToWasm0(config_path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 377 |
+
const len2 = WASM_VECTOR_LEN;
|
| 378 |
+
const ptr3 = passArray8ToWasm0(config_json, wasm.__wbindgen_malloc);
|
| 379 |
+
const len3 = WASM_VECTOR_LEN;
|
| 380 |
+
const ptr4 = passArray8ToWasm0(userland_module, wasm.__wbindgen_malloc);
|
| 381 |
+
const len4 = WASM_VECTOR_LEN;
|
| 382 |
+
const ptr5 = passStringToWasm0(arch, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 383 |
+
const len5 = WASM_VECTOR_LEN;
|
| 384 |
+
const ret = wasm.console_run_devcontainer(this.__wbg_ptr, ptr0, len0, ptr1, len1, ptr2, len2, ptr3, len3, ptr4, len4, ptr5, len5, memory_bytes);
|
| 385 |
+
var ptr7 = ret[0];
|
| 386 |
+
var len7 = ret[1];
|
| 387 |
+
if (ret[3]) {
|
| 388 |
+
ptr7 = 0; len7 = 0;
|
| 389 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 390 |
+
}
|
| 391 |
+
deferred8_0 = ptr7;
|
| 392 |
+
deferred8_1 = len7;
|
| 393 |
+
return getStringFromWasm0(ptr7, len7);
|
| 394 |
+
} finally {
|
| 395 |
+
wasm.__wbindgen_free(deferred8_0, deferred8_1, 1);
|
| 396 |
+
}
|
| 397 |
+
}
|
| 398 |
+
/**
|
| 399 |
+
* Sign in by unlocking a self-sovereign key (not a server account,
|
| 400 |
+
* ADR-001). Returns the operator's content-addressed identity κ.
|
| 401 |
+
* @param {Uint8Array} key
|
| 402 |
+
* @returns {string}
|
| 403 |
+
*/
|
| 404 |
+
sign_in(key) {
|
| 405 |
+
let deferred2_0;
|
| 406 |
+
let deferred2_1;
|
| 407 |
+
try {
|
| 408 |
+
const ptr0 = passArray8ToWasm0(key, wasm.__wbindgen_malloc);
|
| 409 |
+
const len0 = WASM_VECTOR_LEN;
|
| 410 |
+
const ret = wasm.console_sign_in(this.__wbg_ptr, ptr0, len0);
|
| 411 |
+
deferred2_0 = ret[0];
|
| 412 |
+
deferred2_1 = ret[1];
|
| 413 |
+
return getStringFromWasm0(ret[0], ret[1]);
|
| 414 |
+
} finally {
|
| 415 |
+
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
| 416 |
+
}
|
| 417 |
+
}
|
| 418 |
+
/**
|
| 419 |
+
* The console's View — a JSON projection of the operator and their
|
| 420 |
+
* holospaces (what the UI renders).
|
| 421 |
+
* @returns {string}
|
| 422 |
+
*/
|
| 423 |
+
view() {
|
| 424 |
+
let deferred1_0;
|
| 425 |
+
let deferred1_1;
|
| 426 |
+
try {
|
| 427 |
+
const ret = wasm.console_view(this.__wbg_ptr);
|
| 428 |
+
deferred1_0 = ret[0];
|
| 429 |
+
deferred1_1 = ret[1];
|
| 430 |
+
return getStringFromWasm0(ret[0], ret[1]);
|
| 431 |
+
} finally {
|
| 432 |
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
| 433 |
+
}
|
| 434 |
+
}
|
| 435 |
+
}
|
| 436 |
+
if (Symbol.dispose) Console.prototype[Symbol.dispose] = Console.prototype.free;
|
| 437 |
+
|
| 438 |
+
/**
|
| 439 |
+
* A devcontainer's OCI image, assembled into a bootable root filesystem *in the
|
| 440 |
+
* browser* — the Layer Assembler (`CC-7` / the in-crate ext4 writer) running as
|
| 441 |
+
* the wasm peer. The operator's page fetches the devcontainer's image layers
|
| 442 |
+
* from the cold-start gateway (verified by re-derivation before they are added),
|
| 443 |
+
* then assembles them here; the result boots over the emulator's `virtio-blk`
|
| 444 |
+
* ([`Workspace::boot_devcontainer`], `CC-14`). The browser peer *is* the
|
| 445 |
+
* machine — no server assembles or boots the OS (Law L1/L4).
|
| 446 |
+
*/
|
| 447 |
+
export class DevcontainerImage {
|
| 448 |
+
__destroy_into_raw() {
|
| 449 |
+
const ptr = this.__wbg_ptr;
|
| 450 |
+
this.__wbg_ptr = 0;
|
| 451 |
+
DevcontainerImageFinalization.unregister(this);
|
| 452 |
+
return ptr;
|
| 453 |
+
}
|
| 454 |
+
free() {
|
| 455 |
+
const ptr = this.__destroy_into_raw();
|
| 456 |
+
wasm.__wbg_devcontainerimage_free(ptr, 0);
|
| 457 |
+
}
|
| 458 |
+
/**
|
| 459 |
+
* Add an OCI image layer (its media type + the verified blob bytes), in
|
| 460 |
+
* order from the base layer up.
|
| 461 |
+
* @param {string} media_type
|
| 462 |
+
* @param {Uint8Array} blob
|
| 463 |
+
*/
|
| 464 |
+
add_layer(media_type, blob) {
|
| 465 |
+
const ptr0 = passStringToWasm0(media_type, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 466 |
+
const len0 = WASM_VECTOR_LEN;
|
| 467 |
+
const ptr1 = passArray8ToWasm0(blob, wasm.__wbindgen_malloc);
|
| 468 |
+
const len1 = WASM_VECTOR_LEN;
|
| 469 |
+
wasm.devcontainerimage_add_layer(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
| 470 |
+
}
|
| 471 |
+
/**
|
| 472 |
+
* Assemble the layers into a bootable `ext4` root filesystem (gunzip +
|
| 473 |
+
* untar + OCI whiteout overlay + the in-crate ext4 writer; Law L4). The
|
| 474 |
+
* bytes back a [`Workspace::boot_devcontainer`] machine's `virtio-blk` disk.
|
| 475 |
+
* @returns {Uint8Array}
|
| 476 |
+
*/
|
| 477 |
+
assemble() {
|
| 478 |
+
const ret = wasm.devcontainerimage_assemble(this.__wbg_ptr);
|
| 479 |
+
if (ret[3]) {
|
| 480 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 481 |
+
}
|
| 482 |
+
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 483 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 484 |
+
return v1;
|
| 485 |
+
}
|
| 486 |
+
/**
|
| 487 |
+
* Assemble the layers into a **bootable, interactive, writable** root
|
| 488 |
+
* filesystem on a `disk_bytes`-sized disk: the same overlay as
|
| 489 |
+
* [`Self::assemble`], plus the persistent devcontainer
|
| 490 |
+
* [`/init`](holospaces::machine::DEVCONTAINER_INIT) injected — it mounts the
|
| 491 |
+
* pseudo filesystems and the shared `virtio-9p` workspace and execs a shell,
|
| 492 |
+
* so the booted OS stays running as a dev environment instead of powering off
|
| 493 |
+
* after boot — and sized to `disk_bytes` so the OS has room to work (the
|
| 494 |
+
* devcontainer's disk; the caller's to choose, not a hidden cap). The base
|
| 495 |
+
* image must provide a static `/bin/busybox`.
|
| 496 |
+
* @param {number} disk_bytes
|
| 497 |
+
* @returns {Uint8Array}
|
| 498 |
+
*/
|
| 499 |
+
assemble_bootable(disk_bytes) {
|
| 500 |
+
const ret = wasm.devcontainerimage_assemble_bootable(this.__wbg_ptr, disk_bytes);
|
| 501 |
+
if (ret[3]) {
|
| 502 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 503 |
+
}
|
| 504 |
+
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 505 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 506 |
+
return v1;
|
| 507 |
+
}
|
| 508 |
+
/**
|
| 509 |
+
* Like [`Self::assemble_bootable_real`], but stream the ext4 image **straight
|
| 510 |
+
* into an OPFS file** (`CC-50`, the sparse streaming Rootfs Assembly): only the
|
| 511 |
+
* non-zero blocks are written (the free data region stays a sparse hole), so
|
| 512 |
+
* even a multi-GiB disk assembles **without ever materializing a dense buffer**
|
| 513 |
+
* in wasm RAM — peak working memory tracks the image's *content*, not its
|
| 514 |
+
* declared size ("the KappaStore IS the memory, RAM is a cache", Laws L3/L4).
|
| 515 |
+
* `rootfs_handle` is a worker sync access handle on the rootfs file; returns the
|
| 516 |
+
* total image length (a whole number of sectors). The page then boots it
|
| 517 |
+
* off-heap with [`Workspace::boot_devcontainer_opfs_streamed`] — neither
|
| 518 |
+
* provisioning nor boot ever holds the whole image in RAM (the lean,
|
| 519 |
+
* substrate-native path).
|
| 520 |
+
* @param {FileSystemSyncAccessHandle} rootfs_handle
|
| 521 |
+
* @param {number} disk_bytes
|
| 522 |
+
* @param {boolean} real
|
| 523 |
+
* @returns {number}
|
| 524 |
+
*/
|
| 525 |
+
assemble_bootable_into_opfs(rootfs_handle, disk_bytes, real) {
|
| 526 |
+
const ret = wasm.devcontainerimage_assemble_bootable_into_opfs(this.__wbg_ptr, rootfs_handle, disk_bytes, real);
|
| 527 |
+
if (ret[2]) {
|
| 528 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 529 |
+
}
|
| 530 |
+
return ret[0];
|
| 531 |
+
}
|
| 532 |
+
/**
|
| 533 |
+
* Like [`Self::assemble_bootable`], but inject the **real-OCI-image** `/init`
|
| 534 |
+
* ([`REAL_IMAGE_INIT`](holospaces::machine::REAL_IMAGE_INIT), `CC-42`) — for a
|
| 535 |
+
* repository's own base image (debian, ubuntu, buildpack-deps) that ships its
|
| 536 |
+
* own `/bin/sh` + coreutils + `mount` rather than a static `/bin/busybox`. It
|
| 537 |
+
* mounts the pseudo-filesystems + the shared `virtio-9p` workspace with the
|
| 538 |
+
* image's tools and execs its login shell (`bash` if present, else `sh`), so a
|
| 539 |
+
* stock Debian image boots to its real interactive shell.
|
| 540 |
+
* @param {number} disk_bytes
|
| 541 |
+
* @returns {Uint8Array}
|
| 542 |
+
*/
|
| 543 |
+
assemble_bootable_real(disk_bytes) {
|
| 544 |
+
const ret = wasm.devcontainerimage_assemble_bootable_real(this.__wbg_ptr, disk_bytes);
|
| 545 |
+
if (ret[3]) {
|
| 546 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 547 |
+
}
|
| 548 |
+
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 549 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 550 |
+
return v1;
|
| 551 |
+
}
|
| 552 |
+
/**
|
| 553 |
+
* A new, empty image (add its layers lowest-first with [`Self::add_layer`]).
|
| 554 |
+
*/
|
| 555 |
+
constructor() {
|
| 556 |
+
const ret = wasm.devcontainerimage_new();
|
| 557 |
+
this.__wbg_ptr = ret;
|
| 558 |
+
DevcontainerImageFinalization.register(this, this.__wbg_ptr, this);
|
| 559 |
+
return this;
|
| 560 |
+
}
|
| 561 |
+
}
|
| 562 |
+
if (Symbol.dispose) DevcontainerImage.prototype[Symbol.dispose] = DevcontainerImage.prototype.free;
|
| 563 |
+
|
| 564 |
+
/**
|
| 565 |
+
* **The interactive userland Workspace** — a real CC-6 Wasm code module (e.g.
|
| 566 |
+
* `qemu-system-x86_64` compiled to wasm32 over the hologram host ABI) run *as a
|
| 567 |
+
* live, interactive holospace* in the browser tab (ADR-008/009; `CC-6` + the
|
| 568 |
+
* interactive `CC-11` console). Where [`Console::boot_userland`] proves the
|
| 569 |
+
* one-shot lifecycle (boot → suspend → resume → terminate) through the substrate
|
| 570 |
+
* runtime, this drives the *same validated module* on the *same `wasmi`
|
| 571 |
+
* interpreter* the engine uses — pumped in cooperative slices with a live
|
| 572 |
+
* console, so the operator gets a real terminal (the QEMU holospace; see
|
| 573 |
+
* `web/QEMU-HOLOSPACE.md`).
|
| 574 |
+
*
|
| 575 |
+
* **Why a direct `wasmi` driver, not `Session`/`ContainerRuntime`.** The browser
|
| 576 |
+
* `ContainerEngine` (hologram-runtime-bare) wires an *empty* `Linker`: a CC-6
|
| 577 |
+
* container has **no** `hologram.*` host imports, and the one-shot `spawn /
|
| 578 |
+
* suspend / resume / terminate` runtime exposes neither a sub-budget run nor a
|
| 579 |
+
* console stream. So an interactive QEMU communicates the only way that surface
|
| 580 |
+
* allows — through **linear memory** between calls (a mailbox), exactly as the
|
| 581 |
+
* emulator [`Workspace`] drives the native [`Emulator`](holospaces::emulator)
|
| 582 |
+
* directly rather than through the runtime. The module is still gated by
|
| 583 |
+
* [`validate_userland`] (the `CC-6` host-ABI surface) before it runs, and is
|
| 584 |
+
* still a self-contained substrate userland (it imports nothing).
|
| 585 |
+
*
|
| 586 |
+
* **The container ABI (the contract `build-qemu-wasm.sh` must satisfy).** The
|
| 587 |
+
* module exports `memory` and: `hg_mailbox() -> i32` (the control struct's byte
|
| 588 |
+
* offset); `hg_init() -> i32` (build the machine from the cfg region — argv +
|
| 589 |
+
* disk the host wrote; `0` = ok); `hg_event(budget: i32) -> i32` (drain stdin
|
| 590 |
+
* from `in`, run up to `budget` guest instructions as a cooperative TCG/TCI slice
|
| 591 |
+
* that **returns** rather than blocking, append console bytes to `out`, set
|
| 592 |
+
* `halted`; `0` = ok, a trap halts); and optional `hg_suspend()`/`hg_resume()`
|
| 593 |
+
* flush/rehydrate around the snapshot. The mailbox is a little-endian `u32`
|
| 594 |
+
* struct: `magic, abi, cfg_ptr, cfg_cap, cfg_len, in_ptr, in_cap, in_len,
|
| 595 |
+
* out_ptr, out_cap, out_len, halted`. The snapshot is taken at an `hg_event`
|
| 596 |
+
* boundary (the C stack unwound), so linear memory holds all persistent machine
|
| 597 |
+
* state (CPU, RAM, devices) and transient Wasm globals need not be carried
|
| 598 |
+
* (`CC-30`/`CC-31`).
|
| 599 |
+
*/
|
| 600 |
+
export class UserlandWorkspace {
|
| 601 |
+
static __wrap(ptr) {
|
| 602 |
+
const obj = Object.create(UserlandWorkspace.prototype);
|
| 603 |
+
obj.__wbg_ptr = ptr;
|
| 604 |
+
UserlandWorkspaceFinalization.register(obj, obj.__wbg_ptr, obj);
|
| 605 |
+
return obj;
|
| 606 |
+
}
|
| 607 |
+
__destroy_into_raw() {
|
| 608 |
+
const ptr = this.__wbg_ptr;
|
| 609 |
+
this.__wbg_ptr = 0;
|
| 610 |
+
UserlandWorkspaceFinalization.unregister(this);
|
| 611 |
+
return ptr;
|
| 612 |
+
}
|
| 613 |
+
free() {
|
| 614 |
+
const ptr = this.__destroy_into_raw();
|
| 615 |
+
wasm.__wbg_userlandworkspace_free(ptr, 0);
|
| 616 |
+
}
|
| 617 |
+
/**
|
| 618 |
+
* Boot a CC-6 module as an interactive holospace. `module` is the QEMU wasm
|
| 619 |
+
* (validated against the host-ABI surface, `CC-6`); `argv` is the QEMU command
|
| 620 |
+
* line, one argument per line; `disk` is the guest image (held in the guest's
|
| 621 |
+
* linear memory in v1 — a large disk needs the staged κ-disk sector protocol).
|
| 622 |
+
* `memory_bytes` and `relay_url` are reserved (the guest sizes its own RAM
|
| 623 |
+
* today; networking is the staged `CC-16` step).
|
| 624 |
+
* @param {Uint8Array} module
|
| 625 |
+
* @param {string} argv
|
| 626 |
+
* @param {Uint8Array} disk
|
| 627 |
+
* @param {number} memory_bytes
|
| 628 |
+
* @param {string} relay_url
|
| 629 |
+
* @returns {UserlandWorkspace}
|
| 630 |
+
*/
|
| 631 |
+
static boot(module, argv, disk, memory_bytes, relay_url) {
|
| 632 |
+
const ptr0 = passArray8ToWasm0(module, wasm.__wbindgen_malloc);
|
| 633 |
+
const len0 = WASM_VECTOR_LEN;
|
| 634 |
+
const ptr1 = passStringToWasm0(argv, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 635 |
+
const len1 = WASM_VECTOR_LEN;
|
| 636 |
+
const ptr2 = passArray8ToWasm0(disk, wasm.__wbindgen_malloc);
|
| 637 |
+
const len2 = WASM_VECTOR_LEN;
|
| 638 |
+
const ptr3 = passStringToWasm0(relay_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 639 |
+
const len3 = WASM_VECTOR_LEN;
|
| 640 |
+
const ret = wasm.userlandworkspace_boot(ptr0, len0, ptr1, len1, ptr2, len2, memory_bytes, ptr3, len3);
|
| 641 |
+
if (ret[2]) {
|
| 642 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 643 |
+
}
|
| 644 |
+
return UserlandWorkspace.__wrap(ret[0]);
|
| 645 |
+
}
|
| 646 |
+
/**
|
| 647 |
+
* Feed **raw terminal input** (keystrokes, control bytes, escape sequences) to
|
| 648 |
+
* the guest — queued, then delivered on the next [`run`](Self::run), so the
|
| 649 |
+
* guest's own tty echoes and edits and Ctrl-C raises SIGINT (a real terminal,
|
| 650 |
+
* `CC-11`). The input is part of the machine's state captured by the snapshot.
|
| 651 |
+
* @param {Uint8Array} bytes
|
| 652 |
+
*/
|
| 653 |
+
feed_input(bytes) {
|
| 654 |
+
const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
|
| 655 |
+
const len0 = WASM_VECTOR_LEN;
|
| 656 |
+
wasm.userlandworkspace_feed_input(this.__wbg_ptr, ptr0, len0);
|
| 657 |
+
}
|
| 658 |
+
/**
|
| 659 |
+
* Whether the guest has powered off.
|
| 660 |
+
* @returns {boolean}
|
| 661 |
+
*/
|
| 662 |
+
get halted() {
|
| 663 |
+
const ret = wasm.userlandworkspace_halted(this.__wbg_ptr);
|
| 664 |
+
return ret !== 0;
|
| 665 |
+
}
|
| 666 |
+
/**
|
| 667 |
+
* Resume an interactive userland from a linear-memory snapshot
|
| 668 |
+
* [`suspend`](Self::suspend) produced. `module` is the same CC-6 module (it is
|
| 669 |
+
* content — re-fetched and verified, not carried in the snapshot); the
|
| 670 |
+
* snapshot is the machine's memory at an `hg_event` boundary (`CC-30`/`CC-31`).
|
| 671 |
+
* @param {Uint8Array} module
|
| 672 |
+
* @param {Uint8Array} snapshot
|
| 673 |
+
* @returns {UserlandWorkspace}
|
| 674 |
+
*/
|
| 675 |
+
static resume(module, snapshot) {
|
| 676 |
+
const ptr0 = passArray8ToWasm0(module, wasm.__wbindgen_malloc);
|
| 677 |
+
const len0 = WASM_VECTOR_LEN;
|
| 678 |
+
const ptr1 = passArray8ToWasm0(snapshot, wasm.__wbindgen_malloc);
|
| 679 |
+
const len1 = WASM_VECTOR_LEN;
|
| 680 |
+
const ret = wasm.userlandworkspace_resume(ptr0, len0, ptr1, len1);
|
| 681 |
+
if (ret[2]) {
|
| 682 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 683 |
+
}
|
| 684 |
+
return UserlandWorkspace.__wrap(ret[0]);
|
| 685 |
+
}
|
| 686 |
+
/**
|
| 687 |
+
* Advance the guest by up to `budget` instructions — one cooperative slice.
|
| 688 |
+
* Delivers any queued [`feed_input`](Self::feed_input) to the guest first,
|
| 689 |
+
* then drains the console it produced. Returns `true` once it has halted. Call
|
| 690 |
+
* repeatedly from a UI loop, streaming [`terminal_delta`](Self::terminal_delta)
|
| 691 |
+
* between slices.
|
| 692 |
+
* @param {number} budget
|
| 693 |
+
* @returns {boolean}
|
| 694 |
+
*/
|
| 695 |
+
run(budget) {
|
| 696 |
+
const ret = wasm.userlandworkspace_run(this.__wbg_ptr, budget);
|
| 697 |
+
return ret !== 0;
|
| 698 |
+
}
|
| 699 |
+
/**
|
| 700 |
+
* Suspend to a κ snapshot — the guest's linear memory at this `hg_event`
|
| 701 |
+
* boundary, the canonical content-addressed machine state (`CC-30`). The
|
| 702 |
+
* browser persists it (gzipped) to OPFS so the next launch resumes (`CC-31`).
|
| 703 |
+
* @returns {Uint8Array}
|
| 704 |
+
*/
|
| 705 |
+
suspend() {
|
| 706 |
+
const ret = wasm.userlandworkspace_suspend(this.__wbg_ptr);
|
| 707 |
+
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 708 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 709 |
+
return v1;
|
| 710 |
+
}
|
| 711 |
+
/**
|
| 712 |
+
* The console bytes produced **since the last call** (an internal cursor) —
|
| 713 |
+
* the integrated terminal's render delta, O(new) not O(total) per frame.
|
| 714 |
+
* @returns {Uint8Array}
|
| 715 |
+
*/
|
| 716 |
+
terminal_delta() {
|
| 717 |
+
const ret = wasm.userlandworkspace_terminal_delta(this.__wbg_ptr);
|
| 718 |
+
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 719 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 720 |
+
return v1;
|
| 721 |
+
}
|
| 722 |
+
}
|
| 723 |
+
if (Symbol.dispose) UserlandWorkspace.prototype[Symbol.dispose] = UserlandWorkspace.prototype.free;
|
| 724 |
+
|
| 725 |
+
/**
|
| 726 |
+
* A **workspace** over a running holospace, in the browser tab — the
|
| 727 |
+
* Codespaces/Gitpod experience (ADR-009; `CC-9` + `CC-11`). The operator
|
| 728 |
+
* launches a holospace whose code is the system emulator; it **boots a real
|
| 729 |
+
* operating system** (the [system emulator](holospaces::emulator) running in
|
| 730 |
+
* the browser's own wasm engine), and the [workspace
|
| 731 |
+
* projection](holospaces::projection) drives it: a live **terminal**
|
| 732 |
+
* (keystrokes published as canonical events that advance the holospace's κ
|
| 733 |
+
* snapshot) and an **editor** that reads and edits environment content *by κ*.
|
| 734 |
+
*
|
| 735 |
+
* The boot runs in instruction *chunks* ([`run`](Workspace::run)) so the UI
|
| 736 |
+
* stays responsive and can stream the console as the kernel boots — there is no
|
| 737 |
+
* server doing the work; the browser peer *is* the machine (Law L1).
|
| 738 |
+
*/
|
| 739 |
+
export class Workspace {
|
| 740 |
+
static __wrap(ptr) {
|
| 741 |
+
const obj = Object.create(Workspace.prototype);
|
| 742 |
+
obj.__wbg_ptr = ptr;
|
| 743 |
+
WorkspaceFinalization.register(obj, obj.__wbg_ptr, obj);
|
| 744 |
+
return obj;
|
| 745 |
+
}
|
| 746 |
+
__destroy_into_raw() {
|
| 747 |
+
const ptr = this.__wbg_ptr;
|
| 748 |
+
this.__wbg_ptr = 0;
|
| 749 |
+
WorkspaceFinalization.unregister(this);
|
| 750 |
+
return ptr;
|
| 751 |
+
}
|
| 752 |
+
free() {
|
| 753 |
+
const ptr = this.__destroy_into_raw();
|
| 754 |
+
wasm.__wbg_workspace_free(ptr, 0);
|
| 755 |
+
}
|
| 756 |
+
/**
|
| 757 |
+
* Launch a workspace: place the OS `kernel` image and `dtb` in a machine
|
| 758 |
+
* with `ram_bytes` of RAM at `base`, the device tree at `dtb_addr`, and hand
|
| 759 |
+
* off as the SBI firmware. The machine is now booting (drive it with
|
| 760 |
+
* [`run`](Workspace::run)).
|
| 761 |
+
* @param {Uint8Array} kernel
|
| 762 |
+
* @param {Uint8Array} dtb
|
| 763 |
+
* @param {number} ram_bytes
|
| 764 |
+
* @param {number} base
|
| 765 |
+
* @param {number} dtb_addr
|
| 766 |
+
* @returns {Workspace}
|
| 767 |
+
*/
|
| 768 |
+
static boot(kernel, dtb, ram_bytes, base, dtb_addr) {
|
| 769 |
+
const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
|
| 770 |
+
const len0 = WASM_VECTOR_LEN;
|
| 771 |
+
const ptr1 = passArray8ToWasm0(dtb, wasm.__wbindgen_malloc);
|
| 772 |
+
const len1 = WASM_VECTOR_LEN;
|
| 773 |
+
const ret = wasm.workspace_boot(ptr0, len0, ptr1, len1, ram_bytes, base, dtb_addr);
|
| 774 |
+
if (ret[2]) {
|
| 775 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 776 |
+
}
|
| 777 |
+
return Workspace.__wrap(ret[0]);
|
| 778 |
+
}
|
| 779 |
+
/**
|
| 780 |
+
* Boot a **devcontainer** workspace: the Boot Orchestrator
|
| 781 |
+
* ([`MachineSpec`](holospaces::machine::MachineSpec)) generates the device
|
| 782 |
+
* tree and boots `kernel` on a machine whose `virtio-blk` disk is the
|
| 783 |
+
* assembled `rootfs` (from [`DevcontainerImage::assemble`]). The guest
|
| 784 |
+
* kernel mounts the rootfs over `/dev/vda` and runs the devcontainer's real
|
| 785 |
+
* OS — entirely in the browser peer (`CC-14`). Drive it with
|
| 786 |
+
* [`run`](Workspace::run), exactly like [`boot`](Workspace::boot).
|
| 787 |
+
* @param {Uint8Array} kernel
|
| 788 |
+
* @param {Uint8Array} rootfs
|
| 789 |
+
* @returns {Workspace}
|
| 790 |
+
*/
|
| 791 |
+
static boot_devcontainer(kernel, rootfs) {
|
| 792 |
+
const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
|
| 793 |
+
const len0 = WASM_VECTOR_LEN;
|
| 794 |
+
const ptr1 = passArray8ToWasm0(rootfs, wasm.__wbindgen_malloc);
|
| 795 |
+
const len1 = WASM_VECTOR_LEN;
|
| 796 |
+
const ret = wasm.workspace_boot_devcontainer(ptr0, len0, ptr1, len1);
|
| 797 |
+
if (ret[2]) {
|
| 798 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 799 |
+
}
|
| 800 |
+
return Workspace.__wrap(ret[0]);
|
| 801 |
+
}
|
| 802 |
+
/**
|
| 803 |
+
* Boot a devcontainer with the **in-process loopback bridge** enabled
|
| 804 |
+
* (ADR-020, `CC-33`): the guest's interface comes up with DHCP (so it has a
|
| 805 |
+
* real TCP stack), but instead of a WebSocket egress to the internet it gets
|
| 806 |
+
* a no-op egress and the *loopback ingress* — so the workbench, in this same
|
| 807 |
+
* process, can [`dial_guest`](Workspace::dial_guest) a server *inside* the
|
| 808 |
+
* devcontainer (a language server, a remote extension host) and exchange a
|
| 809 |
+
* byte stream with it, with no relay or socket. This is the transport the VS
|
| 810 |
+
* Code remote model runs over in the browser peer (ADR-015/ADR-020). Drive it
|
| 811 |
+
* with [`run`](Workspace::run), pumping the NAT so the bridge's bytes flow.
|
| 812 |
+
* @param {Uint8Array} kernel
|
| 813 |
+
* @param {Uint8Array} rootfs
|
| 814 |
+
* @returns {Workspace}
|
| 815 |
+
*/
|
| 816 |
+
static boot_devcontainer_bridged(kernel, rootfs) {
|
| 817 |
+
const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
|
| 818 |
+
const len0 = WASM_VECTOR_LEN;
|
| 819 |
+
const ptr1 = passArray8ToWasm0(rootfs, wasm.__wbindgen_malloc);
|
| 820 |
+
const len1 = WASM_VECTOR_LEN;
|
| 821 |
+
const ret = wasm.workspace_boot_devcontainer_bridged(ptr0, len0, ptr1, len1);
|
| 822 |
+
if (ret[2]) {
|
| 823 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 824 |
+
}
|
| 825 |
+
return Workspace.__wrap(ret[0]);
|
| 826 |
+
}
|
| 827 |
+
/**
|
| 828 |
+
* Boot a **networked** devcontainer workspace (`CC-16`): like
|
| 829 |
+
* [`boot_devcontainer`](Workspace::boot_devcontainer), but the machine also
|
| 830 |
+
* has a `virtio-net` device whose userspace TCP/IP NAT tunnels the guest's
|
| 831 |
+
* TCP streams out over a WebSocket to the relay at `relay_url` (there is no
|
| 832 |
+
* raw NIC behind a tab; ADR-014). The guest brings its interface up with
|
| 833 |
+
* DHCP and can then reach the internet — `git clone`, `apt`, `npm` — from the
|
| 834 |
+
* browser peer. Drive it with [`run`](Workspace::run), yielding to the event
|
| 835 |
+
* loop between chunks so the WebSocket delivers host-side bytes.
|
| 836 |
+
* @param {Uint8Array} kernel
|
| 837 |
+
* @param {Uint8Array} rootfs
|
| 838 |
+
* @param {string} relay_url
|
| 839 |
+
* @returns {Workspace}
|
| 840 |
+
*/
|
| 841 |
+
static boot_devcontainer_net(kernel, rootfs, relay_url) {
|
| 842 |
+
const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
|
| 843 |
+
const len0 = WASM_VECTOR_LEN;
|
| 844 |
+
const ptr1 = passArray8ToWasm0(rootfs, wasm.__wbindgen_malloc);
|
| 845 |
+
const len1 = WASM_VECTOR_LEN;
|
| 846 |
+
const ptr2 = passStringToWasm0(relay_url, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 847 |
+
const len2 = WASM_VECTOR_LEN;
|
| 848 |
+
const ret = wasm.workspace_boot_devcontainer_net(ptr0, len0, ptr1, len1, ptr2, len2);
|
| 849 |
+
if (ret[2]) {
|
| 850 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 851 |
+
}
|
| 852 |
+
return Workspace.__wrap(ret[0]);
|
| 853 |
+
}
|
| 854 |
+
/**
|
| 855 |
+
* Boot a devcontainer with the disk **streamed** off-heap into an OPFS-backed
|
| 856 |
+
* κ-store (the substrate-native, lean path — ADR/CC-9.d). `rootfs_handle` is a
|
| 857 |
+
* sync access handle on the provisioned rootfs file (read sector-by-sector,
|
| 858 |
+
* never materialized as a `Vec` in wasm RAM); `disk_handle` is the OPFS κ-store
|
| 859 |
+
* pack. Identical sectors dedup to one κ and all-zero sectors stay sparse, so a
|
| 860 |
+
* real image boots without holding it in RAM — "the KappaStore IS the memory,
|
| 861 |
+
* RAM is a cache" (Law L3). Non-networked (busybox/Debian dev box); drive with
|
| 862 |
+
* [`run`](Workspace::run).
|
| 863 |
+
* @param {Uint8Array} kernel
|
| 864 |
+
* @param {FileSystemSyncAccessHandle} rootfs_handle
|
| 865 |
+
* @param {FileSystemSyncAccessHandle} disk_handle
|
| 866 |
+
* @returns {Workspace}
|
| 867 |
+
*/
|
| 868 |
+
static boot_devcontainer_opfs_streamed(kernel, rootfs_handle, disk_handle) {
|
| 869 |
+
const ptr0 = passArray8ToWasm0(kernel, wasm.__wbindgen_malloc);
|
| 870 |
+
const len0 = WASM_VECTOR_LEN;
|
| 871 |
+
const ret = wasm.workspace_boot_devcontainer_opfs_streamed(ptr0, len0, rootfs_handle, disk_handle);
|
| 872 |
+
if (ret[2]) {
|
| 873 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 874 |
+
}
|
| 875 |
+
return Workspace.__wrap(ret[0]);
|
| 876 |
+
}
|
| 877 |
+
/**
|
| 878 |
+
* The κ of every operator event published on the terminal channel so far.
|
| 879 |
+
* @returns {any[]}
|
| 880 |
+
*/
|
| 881 |
+
channel() {
|
| 882 |
+
const ret = wasm.workspace_channel(this.__wbg_ptr);
|
| 883 |
+
var v1 = getArrayJsValueFromWasm0(ret[0], ret[1]).slice();
|
| 884 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 4, 4);
|
| 885 |
+
return v1;
|
| 886 |
+
}
|
| 887 |
+
/**
|
| 888 |
+
* Dial an in-process connection to a server *inside* the devcontainer,
|
| 889 |
+
* listening on `guest_port`, over the loopback substrate bridge (ADR-020,
|
| 890 |
+
* `CC-33`). Returns the connection id, or `None` if the machine was not booted
|
| 891 |
+
* with the bridge ([`boot_devcontainer_bridged`](Workspace::boot_devcontainer_bridged)).
|
| 892 |
+
* The workbench uses this to reach a language server / the remote extension
|
| 893 |
+
* host (ADR-015) without a relay or socket. Pump with [`run`](Workspace::run)
|
| 894 |
+
* so the NAT opens the connection and the byte stream flows.
|
| 895 |
+
* @param {number} guest_port
|
| 896 |
+
* @returns {number | undefined}
|
| 897 |
+
*/
|
| 898 |
+
dial_guest(guest_port) {
|
| 899 |
+
const ret = wasm.workspace_dial_guest(this.__wbg_ptr, guest_port);
|
| 900 |
+
return ret === Number.MAX_SAFE_INTEGER ? undefined : ret;
|
| 901 |
+
}
|
| 902 |
+
/**
|
| 903 |
+
* Feed **raw terminal input** to the running holospace — the bytes an
|
| 904 |
+
* interactive terminal delivers for each keystroke, *unbuffered*: ordinary
|
| 905 |
+
* characters, control bytes (Ctrl-C = `0x03`, Ctrl-D = `0x04`), and escape
|
| 906 |
+
* sequences (arrows, Home/End). Unlike [`Workspace::type_line`] this does not
|
| 907 |
+
* line-buffer or block: the bytes go to the guest console and the caller's
|
| 908 |
+
* render loop ([`Workspace::run`] + [`Workspace::terminal_delta`]) advances
|
| 909 |
+
* the machine, so the guest's own tty echoes and edits the line and Ctrl-C
|
| 910 |
+
* raises SIGINT — a real terminal, not a line submitter. The input is part of
|
| 911 |
+
* the machine's canonical state (it is captured in the κ snapshot), so the
|
| 912 |
+
* session stays reproducible (Law L1).
|
| 913 |
+
* @param {Uint8Array} bytes
|
| 914 |
+
*/
|
| 915 |
+
feed_input(bytes) {
|
| 916 |
+
const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
|
| 917 |
+
const len0 = WASM_VECTOR_LEN;
|
| 918 |
+
wasm.workspace_feed_input(this.__wbg_ptr, ptr0, len0);
|
| 919 |
+
}
|
| 920 |
+
/**
|
| 921 |
+
* The **file tree**: the workspace's files as a JSON array of
|
| 922 |
+
* `{ path, kappa }` — each file's current content κ (its identity, Law L1).
|
| 923 |
+
* What the editor's explorer renders.
|
| 924 |
+
* @returns {string}
|
| 925 |
+
*/
|
| 926 |
+
files() {
|
| 927 |
+
let deferred1_0;
|
| 928 |
+
let deferred1_1;
|
| 929 |
+
try {
|
| 930 |
+
const ret = wasm.workspace_files(this.__wbg_ptr);
|
| 931 |
+
deferred1_0 = ret[0];
|
| 932 |
+
deferred1_1 = ret[1];
|
| 933 |
+
return getStringFromWasm0(ret[0], ret[1]);
|
| 934 |
+
} finally {
|
| 935 |
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
| 936 |
+
}
|
| 937 |
+
}
|
| 938 |
+
/**
|
| 939 |
+
* Close the host side of a loopback connection (`CC-33`).
|
| 940 |
+
* @param {number} id
|
| 941 |
+
*/
|
| 942 |
+
guest_close(id) {
|
| 943 |
+
wasm.workspace_guest_close(this.__wbg_ptr, id);
|
| 944 |
+
}
|
| 945 |
+
/**
|
| 946 |
+
* Whether a loopback connection is still usable — the guest has not closed it,
|
| 947 |
+
* or has but unread bytes remain (`CC-33`).
|
| 948 |
+
* @param {number} id
|
| 949 |
+
* @returns {boolean}
|
| 950 |
+
*/
|
| 951 |
+
guest_is_open(id) {
|
| 952 |
+
const ret = wasm.workspace_guest_is_open(this.__wbg_ptr, id);
|
| 953 |
+
return ret !== 0;
|
| 954 |
+
}
|
| 955 |
+
/**
|
| 956 |
+
* Drain the guest server's reply bytes on a loopback connection (empty until
|
| 957 |
+
* the machine is pumped enough for the stream to advance; `CC-33`).
|
| 958 |
+
* @param {number} id
|
| 959 |
+
* @returns {Uint8Array}
|
| 960 |
+
*/
|
| 961 |
+
guest_recv(id) {
|
| 962 |
+
const ret = wasm.workspace_guest_recv(this.__wbg_ptr, id);
|
| 963 |
+
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 964 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 965 |
+
return v1;
|
| 966 |
+
}
|
| 967 |
+
/**
|
| 968 |
+
* Write bytes toward the guest server on a loopback connection (`CC-33`).
|
| 969 |
+
* @param {number} id
|
| 970 |
+
* @param {Uint8Array} data
|
| 971 |
+
*/
|
| 972 |
+
guest_send(id, data) {
|
| 973 |
+
const ptr0 = passArray8ToWasm0(data, wasm.__wbindgen_malloc);
|
| 974 |
+
const len0 = WASM_VECTOR_LEN;
|
| 975 |
+
wasm.workspace_guest_send(this.__wbg_ptr, id, ptr0, len0);
|
| 976 |
+
}
|
| 977 |
+
/**
|
| 978 |
+
* Whether the machine has powered off.
|
| 979 |
+
* @returns {boolean}
|
| 980 |
+
*/
|
| 981 |
+
get halted() {
|
| 982 |
+
const ret = wasm.workspace_halted(this.__wbg_ptr);
|
| 983 |
+
return ret !== 0;
|
| 984 |
+
}
|
| 985 |
+
/**
|
| 986 |
+
* The editor's read: fetch a file's content *by κ*, verifying it by
|
| 987 |
+
* re-derivation (Law L5). `undefined` if it is not in the workspace store.
|
| 988 |
+
* @param {string} kappa
|
| 989 |
+
* @returns {Uint8Array | undefined}
|
| 990 |
+
*/
|
| 991 |
+
open_file(kappa) {
|
| 992 |
+
const ptr0 = passStringToWasm0(kappa, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 993 |
+
const len0 = WASM_VECTOR_LEN;
|
| 994 |
+
const ret = wasm.workspace_open_file(this.__wbg_ptr, ptr0, len0);
|
| 995 |
+
if (ret[3]) {
|
| 996 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 997 |
+
}
|
| 998 |
+
let v2;
|
| 999 |
+
if (ret[0] !== 0) {
|
| 1000 |
+
v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 1001 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 1002 |
+
}
|
| 1003 |
+
return v2;
|
| 1004 |
+
}
|
| 1005 |
+
/**
|
| 1006 |
+
* Open a file *by path*: the content at the file's current κ (the editor
|
| 1007 |
+
* reads the environment content by κ). `undefined` if the path is unknown.
|
| 1008 |
+
* @param {string} path
|
| 1009 |
+
* @returns {Uint8Array | undefined}
|
| 1010 |
+
*/
|
| 1011 |
+
read_path(path) {
|
| 1012 |
+
const ptr0 = passStringToWasm0(path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1013 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1014 |
+
const ret = wasm.workspace_read_path(this.__wbg_ptr, ptr0, len0);
|
| 1015 |
+
if (ret[3]) {
|
| 1016 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 1017 |
+
}
|
| 1018 |
+
let v2;
|
| 1019 |
+
if (ret[0] !== 0) {
|
| 1020 |
+
v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 1021 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 1022 |
+
}
|
| 1023 |
+
return v2;
|
| 1024 |
+
}
|
| 1025 |
+
/**
|
| 1026 |
+
* **Apply a configuration** the control plane published (ADR-018; `CC-28`):
|
| 1027 |
+
* decode the κ-addressed [`Configuration`] bytes (resolved + verified over
|
| 1028 |
+
* the substrate by the caller, Law L5) and enact its live directives on the
|
| 1029 |
+
* *running* machine — each `forwardPort` begins forwarding on the running
|
| 1030 |
+
* instance, without a reboot. Returns a JSON summary of what was applied
|
| 1031 |
+
* (`{ "forwarded": [{ "guest": 8080, "host": 8080 }], "lifecycle": "…",
|
| 1032 |
+
* "unsupported": [...] }`). The instance state changes from the panel's
|
| 1033 |
+
* configuration, carried as content over the substrate — no RPC.
|
| 1034 |
+
* @param {Uint8Array} config_bytes
|
| 1035 |
+
* @returns {string}
|
| 1036 |
+
*/
|
| 1037 |
+
reconfigure(config_bytes) {
|
| 1038 |
+
let deferred3_0;
|
| 1039 |
+
let deferred3_1;
|
| 1040 |
+
try {
|
| 1041 |
+
const ptr0 = passArray8ToWasm0(config_bytes, wasm.__wbindgen_malloc);
|
| 1042 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1043 |
+
const ret = wasm.workspace_reconfigure(this.__wbg_ptr, ptr0, len0);
|
| 1044 |
+
var ptr2 = ret[0];
|
| 1045 |
+
var len2 = ret[1];
|
| 1046 |
+
if (ret[3]) {
|
| 1047 |
+
ptr2 = 0; len2 = 0;
|
| 1048 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 1049 |
+
}
|
| 1050 |
+
deferred3_0 = ptr2;
|
| 1051 |
+
deferred3_1 = len2;
|
| 1052 |
+
return getStringFromWasm0(ptr2, len2);
|
| 1053 |
+
} finally {
|
| 1054 |
+
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
| 1055 |
+
}
|
| 1056 |
+
}
|
| 1057 |
+
/**
|
| 1058 |
+
* Resume a devcontainer workspace from a κ snapshot [`suspend`](Workspace::suspend)
|
| 1059 |
+
* produced, instead of cold-booting it (`CC-30`). The running OS, its disk,
|
| 1060 |
+
* and the workspace files come back exactly — so a second launch skips the
|
| 1061 |
+
* boot entirely and the editor's content is intact. The snapshot's integrity
|
| 1062 |
+
* is the caller's to check by re-derivation before trusting it across a
|
| 1063 |
+
* session boundary (Law L5; ADR-019) — OPFS is durable but untrusted storage.
|
| 1064 |
+
* @param {Uint8Array} snapshot
|
| 1065 |
+
* @returns {Workspace}
|
| 1066 |
+
*/
|
| 1067 |
+
static resume_devcontainer(snapshot) {
|
| 1068 |
+
const ptr0 = passArray8ToWasm0(snapshot, wasm.__wbindgen_malloc);
|
| 1069 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1070 |
+
const ret = wasm.workspace_resume_devcontainer(ptr0, len0);
|
| 1071 |
+
if (ret[2]) {
|
| 1072 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 1073 |
+
}
|
| 1074 |
+
return Workspace.__wrap(ret[0]);
|
| 1075 |
+
}
|
| 1076 |
+
/**
|
| 1077 |
+
* Resume a **streamed** devcontainer (`CC-30`, the instant warm start for the
|
| 1078 |
+
* lean off-heap κ-disk): like [`resume_devcontainer`](Workspace::resume_devcontainer),
|
| 1079 |
+
* but re-attach the lazy κ-disk's rootfs `loader` from a re-opened OPFS handle —
|
| 1080 |
+
* so the resumed machine pages its *untouched* sectors from the immutable rootfs
|
| 1081 |
+
* again. The content-addressed snapshot carried only the touched disk delta +
|
| 1082 |
+
* the non-zero RAM pages, so resume is O(working set), not O(disk+RAM).
|
| 1083 |
+
* @param {Uint8Array} snapshot
|
| 1084 |
+
* @param {FileSystemSyncAccessHandle} rootfs_handle
|
| 1085 |
+
* @returns {Workspace}
|
| 1086 |
+
*/
|
| 1087 |
+
static resume_devcontainer_streamed(snapshot, rootfs_handle) {
|
| 1088 |
+
const ptr0 = passArray8ToWasm0(snapshot, wasm.__wbindgen_malloc);
|
| 1089 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1090 |
+
const ret = wasm.workspace_resume_devcontainer_streamed(ptr0, len0, rootfs_handle);
|
| 1091 |
+
if (ret[2]) {
|
| 1092 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 1093 |
+
}
|
| 1094 |
+
return Workspace.__wrap(ret[0]);
|
| 1095 |
+
}
|
| 1096 |
+
/**
|
| 1097 |
+
* Advance the running holospace by `budget` instructions (one chunk of the
|
| 1098 |
+
* boot or of servicing input). Returns `true` once the machine has halted
|
| 1099 |
+
* (powered off). Call repeatedly from a UI loop, rendering
|
| 1100 |
+
* [`terminal`](Workspace::terminal) between chunks.
|
| 1101 |
+
* @param {number} budget
|
| 1102 |
+
* @returns {boolean}
|
| 1103 |
+
*/
|
| 1104 |
+
run(budget) {
|
| 1105 |
+
const ret = wasm.workspace_run(this.__wbg_ptr, budget);
|
| 1106 |
+
return ret !== 0;
|
| 1107 |
+
}
|
| 1108 |
+
/**
|
| 1109 |
+
* The **editor** surface: save a file's content (the operator's edit). The
|
| 1110 |
+
* content is κ-addressed into the substrate (Law L2), so the returned κ is
|
| 1111 |
+
* the file's new identity — an edit advances it (Law L1). The canonical edit
|
| 1112 |
+
* event for `path` is published on the channel.
|
| 1113 |
+
* @param {string} path
|
| 1114 |
+
* @param {Uint8Array} content
|
| 1115 |
+
* @returns {string}
|
| 1116 |
+
*/
|
| 1117 |
+
save_file(path, content) {
|
| 1118 |
+
let deferred4_0;
|
| 1119 |
+
let deferred4_1;
|
| 1120 |
+
try {
|
| 1121 |
+
const ptr0 = passStringToWasm0(path, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1122 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1123 |
+
const ptr1 = passArray8ToWasm0(content, wasm.__wbindgen_malloc);
|
| 1124 |
+
const len1 = WASM_VECTOR_LEN;
|
| 1125 |
+
const ret = wasm.workspace_save_file(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
| 1126 |
+
var ptr3 = ret[0];
|
| 1127 |
+
var len3 = ret[1];
|
| 1128 |
+
if (ret[3]) {
|
| 1129 |
+
ptr3 = 0; len3 = 0;
|
| 1130 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 1131 |
+
}
|
| 1132 |
+
deferred4_0 = ptr3;
|
| 1133 |
+
deferred4_1 = len3;
|
| 1134 |
+
return getStringFromWasm0(ptr3, len3);
|
| 1135 |
+
} finally {
|
| 1136 |
+
wasm.__wbindgen_free(deferred4_0, deferred4_1, 1);
|
| 1137 |
+
}
|
| 1138 |
+
}
|
| 1139 |
+
/**
|
| 1140 |
+
* Whether the terminal has rendered `marker` yet (e.g. the ready banner).
|
| 1141 |
+
* @param {string} marker
|
| 1142 |
+
* @returns {boolean}
|
| 1143 |
+
*/
|
| 1144 |
+
shows(marker) {
|
| 1145 |
+
const ptr0 = passStringToWasm0(marker, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1146 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1147 |
+
const ret = wasm.workspace_shows(this.__wbg_ptr, ptr0, len0);
|
| 1148 |
+
return ret !== 0;
|
| 1149 |
+
}
|
| 1150 |
+
/**
|
| 1151 |
+
* The running holospace's κ snapshot — its canonical state (Law L1/L3/L5).
|
| 1152 |
+
* @returns {string}
|
| 1153 |
+
*/
|
| 1154 |
+
state_kappa() {
|
| 1155 |
+
let deferred1_0;
|
| 1156 |
+
let deferred1_1;
|
| 1157 |
+
try {
|
| 1158 |
+
const ret = wasm.workspace_state_kappa(this.__wbg_ptr);
|
| 1159 |
+
deferred1_0 = ret[0];
|
| 1160 |
+
deferred1_1 = ret[1];
|
| 1161 |
+
return getStringFromWasm0(ret[0], ret[1]);
|
| 1162 |
+
} finally {
|
| 1163 |
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
| 1164 |
+
}
|
| 1165 |
+
}
|
| 1166 |
+
/**
|
| 1167 |
+
* Suspend the running machine to a κ snapshot — the canonical,
|
| 1168 |
+
* content-addressed bytes of the whole machine: CPU, RAM, the rootfs disk,
|
| 1169 |
+
* and the *workspace files* (virtio-9p). The browser persists these (gzipped)
|
| 1170 |
+
* to OPFS so the next launch *resumes* instead of cold-booting (`CC-30`).
|
| 1171 |
+
* Most of guest RAM is zero, so the gzipped snapshot is a small fraction of
|
| 1172 |
+
* the machine size.
|
| 1173 |
+
* @returns {Uint8Array}
|
| 1174 |
+
*/
|
| 1175 |
+
suspend() {
|
| 1176 |
+
const ret = wasm.workspace_suspend(this.__wbg_ptr);
|
| 1177 |
+
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 1178 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 1179 |
+
return v1;
|
| 1180 |
+
}
|
| 1181 |
+
/**
|
| 1182 |
+
* The rendered terminal — the console the running holospace has produced.
|
| 1183 |
+
* @returns {string}
|
| 1184 |
+
*/
|
| 1185 |
+
terminal() {
|
| 1186 |
+
let deferred1_0;
|
| 1187 |
+
let deferred1_1;
|
| 1188 |
+
try {
|
| 1189 |
+
const ret = wasm.workspace_terminal(this.__wbg_ptr);
|
| 1190 |
+
deferred1_0 = ret[0];
|
| 1191 |
+
deferred1_1 = ret[1];
|
| 1192 |
+
return getStringFromWasm0(ret[0], ret[1]);
|
| 1193 |
+
} finally {
|
| 1194 |
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
| 1195 |
+
}
|
| 1196 |
+
}
|
| 1197 |
+
/**
|
| 1198 |
+
* The console bytes produced **since the last call** (an internal cursor),
|
| 1199 |
+
* for the integrated terminal's render loop. Returning only the delta avoids
|
| 1200 |
+
* re-reading and re-encoding the whole console each tick — output stays O(new
|
| 1201 |
+
* bytes), not O(total) per frame. Returns raw bytes (the terminal decodes
|
| 1202 |
+
* them); [`Workspace::terminal`] still returns the full buffer for tests.
|
| 1203 |
+
* @returns {Uint8Array}
|
| 1204 |
+
*/
|
| 1205 |
+
terminal_delta() {
|
| 1206 |
+
const ret = wasm.workspace_terminal_delta(this.__wbg_ptr);
|
| 1207 |
+
var v1 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 1208 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 1209 |
+
return v1;
|
| 1210 |
+
}
|
| 1211 |
+
/**
|
| 1212 |
+
* Type a line into the terminal: publish it as a canonical event on the
|
| 1213 |
+
* holospace's channel (Law L1/L2), feed the keystrokes to the running
|
| 1214 |
+
* machine, and run until the response settles. The holospace's κ snapshot
|
| 1215 |
+
* advances. Returns the event's κ.
|
| 1216 |
+
* @param {string} line
|
| 1217 |
+
* @returns {string}
|
| 1218 |
+
*/
|
| 1219 |
+
type_line(line) {
|
| 1220 |
+
let deferred2_0;
|
| 1221 |
+
let deferred2_1;
|
| 1222 |
+
try {
|
| 1223 |
+
const ptr0 = passStringToWasm0(line, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1224 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1225 |
+
const ret = wasm.workspace_type_line(this.__wbg_ptr, ptr0, len0);
|
| 1226 |
+
deferred2_0 = ret[0];
|
| 1227 |
+
deferred2_1 = ret[1];
|
| 1228 |
+
return getStringFromWasm0(ret[0], ret[1]);
|
| 1229 |
+
} finally {
|
| 1230 |
+
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
| 1231 |
+
}
|
| 1232 |
+
}
|
| 1233 |
+
/**
|
| 1234 |
+
* Delete a file or folder from the shared workspace (the workbench
|
| 1235 |
+
* `FileSystemProvider.delete`) — the editor removing content the OS sees
|
| 1236 |
+
* over `virtio-9p`. `true` if it existed.
|
| 1237 |
+
* @param {string} name
|
| 1238 |
+
* @returns {boolean}
|
| 1239 |
+
*/
|
| 1240 |
+
ws_delete(name) {
|
| 1241 |
+
const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1242 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1243 |
+
const ret = wasm.workspace_ws_delete(this.__wbg_ptr, ptr0, len0);
|
| 1244 |
+
return ret !== 0;
|
| 1245 |
+
}
|
| 1246 |
+
/**
|
| 1247 |
+
* The shared workspace's directory listing — a JSON array of
|
| 1248 |
+
* `{ name, dir, size }` over the running holospace's `virtio-9p` workspace
|
| 1249 |
+
* (the workbench `FileSystemProvider.readDirectory`).
|
| 1250 |
+
* @returns {string}
|
| 1251 |
+
*/
|
| 1252 |
+
ws_list() {
|
| 1253 |
+
let deferred1_0;
|
| 1254 |
+
let deferred1_1;
|
| 1255 |
+
try {
|
| 1256 |
+
const ret = wasm.workspace_ws_list(this.__wbg_ptr);
|
| 1257 |
+
deferred1_0 = ret[0];
|
| 1258 |
+
deferred1_1 = ret[1];
|
| 1259 |
+
return getStringFromWasm0(ret[0], ret[1]);
|
| 1260 |
+
} finally {
|
| 1261 |
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
| 1262 |
+
}
|
| 1263 |
+
}
|
| 1264 |
+
/**
|
| 1265 |
+
* Create a folder in the shared workspace (the workbench
|
| 1266 |
+
* `FileSystemProvider.createDirectory`).
|
| 1267 |
+
* @param {string} name
|
| 1268 |
+
*/
|
| 1269 |
+
ws_mkdir(name) {
|
| 1270 |
+
const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1271 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1272 |
+
wasm.workspace_ws_mkdir(this.__wbg_ptr, ptr0, len0);
|
| 1273 |
+
}
|
| 1274 |
+
/**
|
| 1275 |
+
* Read a file from the shared workspace (the workbench
|
| 1276 |
+
* `FileSystemProvider.readFile`) — the same content the OS reads over
|
| 1277 |
+
* `virtio-9p`. `undefined` if absent.
|
| 1278 |
+
* @param {string} name
|
| 1279 |
+
* @returns {Uint8Array | undefined}
|
| 1280 |
+
*/
|
| 1281 |
+
ws_read(name) {
|
| 1282 |
+
const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1283 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1284 |
+
const ret = wasm.workspace_ws_read(this.__wbg_ptr, ptr0, len0);
|
| 1285 |
+
let v2;
|
| 1286 |
+
if (ret[0] !== 0) {
|
| 1287 |
+
v2 = getArrayU8FromWasm0(ret[0], ret[1]).slice();
|
| 1288 |
+
wasm.__wbindgen_free(ret[0], ret[1] * 1, 1);
|
| 1289 |
+
}
|
| 1290 |
+
return v2;
|
| 1291 |
+
}
|
| 1292 |
+
/**
|
| 1293 |
+
* Rename a file or folder in the shared workspace (the workbench
|
| 1294 |
+
* `FileSystemProvider.rename`). `true` if the source existed.
|
| 1295 |
+
* @param {string} from
|
| 1296 |
+
* @param {string} to
|
| 1297 |
+
* @returns {boolean}
|
| 1298 |
+
*/
|
| 1299 |
+
ws_rename(from, to) {
|
| 1300 |
+
const ptr0 = passStringToWasm0(from, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1301 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1302 |
+
const ptr1 = passStringToWasm0(to, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1303 |
+
const len1 = WASM_VECTOR_LEN;
|
| 1304 |
+
const ret = wasm.workspace_ws_rename(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
| 1305 |
+
return ret !== 0;
|
| 1306 |
+
}
|
| 1307 |
+
/**
|
| 1308 |
+
* Write a file into the shared workspace (the workbench
|
| 1309 |
+
* `FileSystemProvider.writeFile`) — the editor saving the *same content* the
|
| 1310 |
+
* OS reads over `virtio-9p` (one content, Law L1). Returns the content's κ
|
| 1311 |
+
* (its identity, Law L1/L2).
|
| 1312 |
+
* @param {string} name
|
| 1313 |
+
* @param {Uint8Array} content
|
| 1314 |
+
* @returns {string}
|
| 1315 |
+
*/
|
| 1316 |
+
ws_write(name, content) {
|
| 1317 |
+
let deferred3_0;
|
| 1318 |
+
let deferred3_1;
|
| 1319 |
+
try {
|
| 1320 |
+
const ptr0 = passStringToWasm0(name, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1321 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1322 |
+
const ptr1 = passArray8ToWasm0(content, wasm.__wbindgen_malloc);
|
| 1323 |
+
const len1 = WASM_VECTOR_LEN;
|
| 1324 |
+
const ret = wasm.workspace_ws_write(this.__wbg_ptr, ptr0, len0, ptr1, len1);
|
| 1325 |
+
deferred3_0 = ret[0];
|
| 1326 |
+
deferred3_1 = ret[1];
|
| 1327 |
+
return getStringFromWasm0(ret[0], ret[1]);
|
| 1328 |
+
} finally {
|
| 1329 |
+
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
| 1330 |
+
}
|
| 1331 |
+
}
|
| 1332 |
+
}
|
| 1333 |
+
if (Symbol.dispose) Workspace.prototype[Symbol.dispose] = Workspace.prototype.free;
|
| 1334 |
+
|
| 1335 |
+
/**
|
| 1336 |
+
* The κ-label of bytes on the substrate's default σ-axis (blake3) — the same
|
| 1337 |
+
* content address every peer computes (Law L1).
|
| 1338 |
+
* @param {Uint8Array} bytes
|
| 1339 |
+
* @returns {string}
|
| 1340 |
+
*/
|
| 1341 |
+
export function kappa(bytes) {
|
| 1342 |
+
let deferred2_0;
|
| 1343 |
+
let deferred2_1;
|
| 1344 |
+
try {
|
| 1345 |
+
const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
|
| 1346 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1347 |
+
const ret = wasm.kappa(ptr0, len0);
|
| 1348 |
+
deferred2_0 = ret[0];
|
| 1349 |
+
deferred2_1 = ret[1];
|
| 1350 |
+
return getStringFromWasm0(ret[0], ret[1]);
|
| 1351 |
+
} finally {
|
| 1352 |
+
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
|
| 1353 |
+
}
|
| 1354 |
+
}
|
| 1355 |
+
|
| 1356 |
+
/**
|
| 1357 |
+
* Run a `.holo` compute artifact in the browser via the hologram executor
|
| 1358 |
+
* compiled to wasm — the *browser `.holo` engine* (arc42 chapter 11, RT2;
|
| 1359 |
+
* conformance `CC-2`). Returns the κ-label of the first output. Because the
|
| 1360 |
+
* executor is deterministic and content-addressed, this κ equals the one the
|
| 1361 |
+
* native executor produces for the same `.holo` (the browser engine equals the
|
| 1362 |
+
* native one).
|
| 1363 |
+
* @param {Uint8Array} archive
|
| 1364 |
+
* @returns {string}
|
| 1365 |
+
*/
|
| 1366 |
+
export function run_holo(archive) {
|
| 1367 |
+
let deferred3_0;
|
| 1368 |
+
let deferred3_1;
|
| 1369 |
+
try {
|
| 1370 |
+
const ptr0 = passArray8ToWasm0(archive, wasm.__wbindgen_malloc);
|
| 1371 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1372 |
+
const ret = wasm.run_holo(ptr0, len0);
|
| 1373 |
+
var ptr2 = ret[0];
|
| 1374 |
+
var len2 = ret[1];
|
| 1375 |
+
if (ret[3]) {
|
| 1376 |
+
ptr2 = 0; len2 = 0;
|
| 1377 |
+
throw takeFromExternrefTable0(ret[2]);
|
| 1378 |
+
}
|
| 1379 |
+
deferred3_0 = ptr2;
|
| 1380 |
+
deferred3_1 = len2;
|
| 1381 |
+
return getStringFromWasm0(ptr2, len2);
|
| 1382 |
+
} finally {
|
| 1383 |
+
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
|
| 1384 |
+
}
|
| 1385 |
+
}
|
| 1386 |
+
|
| 1387 |
+
/**
|
| 1388 |
+
* Validate that `module` is a recompiled userland fit for the *execution
|
| 1389 |
+
* surface* (ADR-008; `CC-6`): specification-valid WebAssembly that imports only
|
| 1390 |
+
* the substrate host ABI and presents the container ABI. This is the κ-boundary
|
| 1391 |
+
* contract the browser peer enforces before a userland may be a holospace's
|
| 1392 |
+
* code — ambient (WASI-style) imports and a missing container ABI are refused.
|
| 1393 |
+
* @param {Uint8Array} module
|
| 1394 |
+
*/
|
| 1395 |
+
export function validate_userland(module) {
|
| 1396 |
+
const ptr0 = passArray8ToWasm0(module, wasm.__wbindgen_malloc);
|
| 1397 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1398 |
+
const ret = wasm.validate_userland(ptr0, len0);
|
| 1399 |
+
if (ret[1]) {
|
| 1400 |
+
throw takeFromExternrefTable0(ret[0]);
|
| 1401 |
+
}
|
| 1402 |
+
}
|
| 1403 |
+
|
| 1404 |
+
/**
|
| 1405 |
+
* Verify bytes against a claimed κ-label by re-derivation (Law L5). This is
|
| 1406 |
+
* what makes content fetched from an untrusted gateway safe.
|
| 1407 |
+
* @param {Uint8Array} bytes
|
| 1408 |
+
* @param {string} kappa
|
| 1409 |
+
* @returns {boolean}
|
| 1410 |
+
*/
|
| 1411 |
+
export function verify_kappa(bytes, kappa) {
|
| 1412 |
+
const ptr0 = passArray8ToWasm0(bytes, wasm.__wbindgen_malloc);
|
| 1413 |
+
const len0 = WASM_VECTOR_LEN;
|
| 1414 |
+
const ptr1 = passStringToWasm0(kappa, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1415 |
+
const len1 = WASM_VECTOR_LEN;
|
| 1416 |
+
const ret = wasm.verify_kappa(ptr0, len0, ptr1, len1);
|
| 1417 |
+
if (ret[2]) {
|
| 1418 |
+
throw takeFromExternrefTable0(ret[1]);
|
| 1419 |
+
}
|
| 1420 |
+
return ret[0] !== 0;
|
| 1421 |
+
}
|
| 1422 |
+
function __wbg_get_imports() {
|
| 1423 |
+
const import0 = {
|
| 1424 |
+
__proto__: null,
|
| 1425 |
+
__wbg___wbindgen_debug_string_0accd80f45e5faa2: function(arg0, arg1) {
|
| 1426 |
+
const ret = debugString(arg1);
|
| 1427 |
+
const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
| 1428 |
+
const len1 = WASM_VECTOR_LEN;
|
| 1429 |
+
getDataViewMemory0().setInt32(arg0 + 4 * 1, len1, true);
|
| 1430 |
+
getDataViewMemory0().setInt32(arg0 + 4 * 0, ptr1, true);
|
| 1431 |
+
},
|
| 1432 |
+
__wbg___wbindgen_throw_1506f2235d1bdba0: function(arg0, arg1) {
|
| 1433 |
+
throw new Error(getStringFromWasm0(arg0, arg1));
|
| 1434 |
+
},
|
| 1435 |
+
__wbg__wbg_cb_unref_61db23ac97f16c31: function(arg0) {
|
| 1436 |
+
arg0._wbg_cb_unref();
|
| 1437 |
+
},
|
| 1438 |
+
__wbg_data_bd354b70c783c66e: function(arg0) {
|
| 1439 |
+
const ret = arg0.data;
|
| 1440 |
+
return ret;
|
| 1441 |
+
},
|
| 1442 |
+
__wbg_getSize_5db8adeff7a4553d: function() { return handleError(function (arg0) {
|
| 1443 |
+
const ret = arg0.getSize();
|
| 1444 |
+
return ret;
|
| 1445 |
+
}, arguments); },
|
| 1446 |
+
__wbg_length_4a591ecaa01354d9: function(arg0) {
|
| 1447 |
+
const ret = arg0.length;
|
| 1448 |
+
return ret;
|
| 1449 |
+
},
|
| 1450 |
+
__wbg_new_578aeef4b6b94378: function(arg0) {
|
| 1451 |
+
const ret = new Uint8Array(arg0);
|
| 1452 |
+
return ret;
|
| 1453 |
+
},
|
| 1454 |
+
__wbg_new_ce1ab61c1c2b300d: function() {
|
| 1455 |
+
const ret = new Object();
|
| 1456 |
+
return ret;
|
| 1457 |
+
},
|
| 1458 |
+
__wbg_new_d7e476b433a26bea: function() { return handleError(function (arg0, arg1) {
|
| 1459 |
+
const ret = new WebSocket(getStringFromWasm0(arg0, arg1));
|
| 1460 |
+
return ret;
|
| 1461 |
+
}, arguments); },
|
| 1462 |
+
__wbg_prototypesetcall_3249fc62a0fafa30: function(arg0, arg1, arg2) {
|
| 1463 |
+
Uint8Array.prototype.set.call(getArrayU8FromWasm0(arg0, arg1), arg2);
|
| 1464 |
+
},
|
| 1465 |
+
__wbg_read_34a0958bcc273c55: function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
| 1466 |
+
const ret = arg0.read(getArrayU8FromWasm0(arg1, arg2), arg3);
|
| 1467 |
+
return ret;
|
| 1468 |
+
}, arguments); },
|
| 1469 |
+
__wbg_send_4a773f523104d75e: function() { return handleError(function (arg0, arg1, arg2) {
|
| 1470 |
+
arg0.send(getArrayU8FromWasm0(arg1, arg2));
|
| 1471 |
+
}, arguments); },
|
| 1472 |
+
__wbg_set_at_5b6d1bf4f66bd626: function(arg0, arg1) {
|
| 1473 |
+
arg0.at = arg1;
|
| 1474 |
+
},
|
| 1475 |
+
__wbg_set_binaryType_41994c453b95bdd2: function(arg0, arg1) {
|
| 1476 |
+
arg0.binaryType = __wbindgen_enum_BinaryType[arg1];
|
| 1477 |
+
},
|
| 1478 |
+
__wbg_set_onclose_13787fb31ae8aefd: function(arg0, arg1) {
|
| 1479 |
+
arg0.onclose = arg1;
|
| 1480 |
+
},
|
| 1481 |
+
__wbg_set_onmessage_9c6b4cb14e244b7f: function(arg0, arg1) {
|
| 1482 |
+
arg0.onmessage = arg1;
|
| 1483 |
+
},
|
| 1484 |
+
__wbg_set_onopen_db452f4233e99d7d: function(arg0, arg1) {
|
| 1485 |
+
arg0.onopen = arg1;
|
| 1486 |
+
},
|
| 1487 |
+
__wbg_truncate_6b32a1fa508f5f05: function() { return handleError(function (arg0, arg1) {
|
| 1488 |
+
arg0.truncate(arg1);
|
| 1489 |
+
}, arguments); },
|
| 1490 |
+
__wbg_write_948386f6a5cf303f: function() { return handleError(function (arg0, arg1, arg2, arg3) {
|
| 1491 |
+
const ret = arg0.write(getArrayU8FromWasm0(arg1, arg2), arg3);
|
| 1492 |
+
return ret;
|
| 1493 |
+
}, arguments); },
|
| 1494 |
+
__wbindgen_cast_0000000000000001: function(arg0, arg1) {
|
| 1495 |
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 11, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
| 1496 |
+
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f);
|
| 1497 |
+
return ret;
|
| 1498 |
+
},
|
| 1499 |
+
__wbindgen_cast_0000000000000002: function(arg0, arg1) {
|
| 1500 |
+
// Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("MessageEvent")], shim_idx: 11, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
|
| 1501 |
+
const ret = makeMutClosure(arg0, arg1, wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f_1);
|
| 1502 |
+
return ret;
|
| 1503 |
+
},
|
| 1504 |
+
__wbindgen_cast_0000000000000003: function(arg0, arg1) {
|
| 1505 |
+
// Cast intrinsic for `Ref(String) -> Externref`.
|
| 1506 |
+
const ret = getStringFromWasm0(arg0, arg1);
|
| 1507 |
+
return ret;
|
| 1508 |
+
},
|
| 1509 |
+
__wbindgen_init_externref_table: function() {
|
| 1510 |
+
const table = wasm.__wbindgen_externrefs;
|
| 1511 |
+
const offset = table.grow(4);
|
| 1512 |
+
table.set(0, undefined);
|
| 1513 |
+
table.set(offset + 0, undefined);
|
| 1514 |
+
table.set(offset + 1, null);
|
| 1515 |
+
table.set(offset + 2, true);
|
| 1516 |
+
table.set(offset + 3, false);
|
| 1517 |
+
},
|
| 1518 |
+
};
|
| 1519 |
+
return {
|
| 1520 |
+
__proto__: null,
|
| 1521 |
+
"./holospaces_web_bg.js": import0,
|
| 1522 |
+
};
|
| 1523 |
+
}
|
| 1524 |
+
|
| 1525 |
+
function wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f(arg0, arg1, arg2) {
|
| 1526 |
+
wasm.wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f(arg0, arg1, arg2);
|
| 1527 |
+
}
|
| 1528 |
+
|
| 1529 |
+
function wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f_1(arg0, arg1, arg2) {
|
| 1530 |
+
wasm.wasm_bindgen__convert__closures_____invoke__h26fdb82a8d18ed9f_1(arg0, arg1, arg2);
|
| 1531 |
+
}
|
| 1532 |
+
|
| 1533 |
+
|
| 1534 |
+
const __wbindgen_enum_BinaryType = ["blob", "arraybuffer"];
|
| 1535 |
+
const Aarch64WorkspaceFinalization = (typeof FinalizationRegistry === 'undefined')
|
| 1536 |
+
? { register: () => {}, unregister: () => {} }
|
| 1537 |
+
: new FinalizationRegistry(ptr => wasm.__wbg_aarch64workspace_free(ptr, 1));
|
| 1538 |
+
const ConsoleFinalization = (typeof FinalizationRegistry === 'undefined')
|
| 1539 |
+
? { register: () => {}, unregister: () => {} }
|
| 1540 |
+
: new FinalizationRegistry(ptr => wasm.__wbg_console_free(ptr, 1));
|
| 1541 |
+
const DevcontainerImageFinalization = (typeof FinalizationRegistry === 'undefined')
|
| 1542 |
+
? { register: () => {}, unregister: () => {} }
|
| 1543 |
+
: new FinalizationRegistry(ptr => wasm.__wbg_devcontainerimage_free(ptr, 1));
|
| 1544 |
+
const UserlandWorkspaceFinalization = (typeof FinalizationRegistry === 'undefined')
|
| 1545 |
+
? { register: () => {}, unregister: () => {} }
|
| 1546 |
+
: new FinalizationRegistry(ptr => wasm.__wbg_userlandworkspace_free(ptr, 1));
|
| 1547 |
+
const WorkspaceFinalization = (typeof FinalizationRegistry === 'undefined')
|
| 1548 |
+
? { register: () => {}, unregister: () => {} }
|
| 1549 |
+
: new FinalizationRegistry(ptr => wasm.__wbg_workspace_free(ptr, 1));
|
| 1550 |
+
|
| 1551 |
+
function addToExternrefTable0(obj) {
|
| 1552 |
+
const idx = wasm.__externref_table_alloc();
|
| 1553 |
+
wasm.__wbindgen_externrefs.set(idx, obj);
|
| 1554 |
+
return idx;
|
| 1555 |
+
}
|
| 1556 |
+
|
| 1557 |
+
const CLOSURE_DTORS = (typeof FinalizationRegistry === 'undefined')
|
| 1558 |
+
? { register: () => {}, unregister: () => {} }
|
| 1559 |
+
: new FinalizationRegistry(state => wasm.__wbindgen_destroy_closure(state.a, state.b));
|
| 1560 |
+
|
| 1561 |
+
function debugString(val) {
|
| 1562 |
+
// primitive types
|
| 1563 |
+
const type = typeof val;
|
| 1564 |
+
if (type == 'number' || type == 'boolean' || val == null) {
|
| 1565 |
+
return `${val}`;
|
| 1566 |
+
}
|
| 1567 |
+
if (type == 'string') {
|
| 1568 |
+
return `"${val}"`;
|
| 1569 |
+
}
|
| 1570 |
+
if (type == 'symbol') {
|
| 1571 |
+
const description = val.description;
|
| 1572 |
+
if (description == null) {
|
| 1573 |
+
return 'Symbol';
|
| 1574 |
+
} else {
|
| 1575 |
+
return `Symbol(${description})`;
|
| 1576 |
+
}
|
| 1577 |
+
}
|
| 1578 |
+
if (type == 'function') {
|
| 1579 |
+
const name = val.name;
|
| 1580 |
+
if (typeof name == 'string' && name.length > 0) {
|
| 1581 |
+
return `Function(${name})`;
|
| 1582 |
+
} else {
|
| 1583 |
+
return 'Function';
|
| 1584 |
+
}
|
| 1585 |
+
}
|
| 1586 |
+
// objects
|
| 1587 |
+
if (Array.isArray(val)) {
|
| 1588 |
+
const length = val.length;
|
| 1589 |
+
let debug = '[';
|
| 1590 |
+
if (length > 0) {
|
| 1591 |
+
debug += debugString(val[0]);
|
| 1592 |
+
}
|
| 1593 |
+
for(let i = 1; i < length; i++) {
|
| 1594 |
+
debug += ', ' + debugString(val[i]);
|
| 1595 |
+
}
|
| 1596 |
+
debug += ']';
|
| 1597 |
+
return debug;
|
| 1598 |
+
}
|
| 1599 |
+
// Test for built-in
|
| 1600 |
+
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
|
| 1601 |
+
let className;
|
| 1602 |
+
if (builtInMatches && builtInMatches.length > 1) {
|
| 1603 |
+
className = builtInMatches[1];
|
| 1604 |
+
} else {
|
| 1605 |
+
// Failed to match the standard '[object ClassName]'
|
| 1606 |
+
return toString.call(val);
|
| 1607 |
+
}
|
| 1608 |
+
if (className == 'Object') {
|
| 1609 |
+
// we're a user defined class or Object
|
| 1610 |
+
// JSON.stringify avoids problems with cycles, and is generally much
|
| 1611 |
+
// easier than looping through ownProperties of `val`.
|
| 1612 |
+
try {
|
| 1613 |
+
return 'Object(' + JSON.stringify(val) + ')';
|
| 1614 |
+
} catch (_) {
|
| 1615 |
+
return 'Object';
|
| 1616 |
+
}
|
| 1617 |
+
}
|
| 1618 |
+
// errors
|
| 1619 |
+
if (val instanceof Error) {
|
| 1620 |
+
return `${val.name}: ${val.message}\n${val.stack}`;
|
| 1621 |
+
}
|
| 1622 |
+
// TODO we could test for more things here, like `Set`s and `Map`s.
|
| 1623 |
+
return className;
|
| 1624 |
+
}
|
| 1625 |
+
|
| 1626 |
+
function getArrayJsValueFromWasm0(ptr, len) {
|
| 1627 |
+
ptr = ptr >>> 0;
|
| 1628 |
+
const mem = getDataViewMemory0();
|
| 1629 |
+
const result = [];
|
| 1630 |
+
for (let i = ptr; i < ptr + 4 * len; i += 4) {
|
| 1631 |
+
result.push(wasm.__wbindgen_externrefs.get(mem.getUint32(i, true)));
|
| 1632 |
+
}
|
| 1633 |
+
wasm.__externref_drop_slice(ptr, len);
|
| 1634 |
+
return result;
|
| 1635 |
+
}
|
| 1636 |
+
|
| 1637 |
+
function getArrayU8FromWasm0(ptr, len) {
|
| 1638 |
+
ptr = ptr >>> 0;
|
| 1639 |
+
return getUint8ArrayMemory0().subarray(ptr / 1, ptr / 1 + len);
|
| 1640 |
+
}
|
| 1641 |
+
|
| 1642 |
+
let cachedDataViewMemory0 = null;
|
| 1643 |
+
function getDataViewMemory0() {
|
| 1644 |
+
if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
|
| 1645 |
+
cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
|
| 1646 |
+
}
|
| 1647 |
+
return cachedDataViewMemory0;
|
| 1648 |
+
}
|
| 1649 |
+
|
| 1650 |
+
function getStringFromWasm0(ptr, len) {
|
| 1651 |
+
return decodeText(ptr >>> 0, len);
|
| 1652 |
+
}
|
| 1653 |
+
|
| 1654 |
+
let cachedUint8ArrayMemory0 = null;
|
| 1655 |
+
function getUint8ArrayMemory0() {
|
| 1656 |
+
if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
|
| 1657 |
+
cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
|
| 1658 |
+
}
|
| 1659 |
+
return cachedUint8ArrayMemory0;
|
| 1660 |
+
}
|
| 1661 |
+
|
| 1662 |
+
function handleError(f, args) {
|
| 1663 |
+
try {
|
| 1664 |
+
return f.apply(this, args);
|
| 1665 |
+
} catch (e) {
|
| 1666 |
+
const idx = addToExternrefTable0(e);
|
| 1667 |
+
wasm.__wbindgen_exn_store(idx);
|
| 1668 |
+
}
|
| 1669 |
+
}
|
| 1670 |
+
|
| 1671 |
+
function makeMutClosure(arg0, arg1, f) {
|
| 1672 |
+
const state = { a: arg0, b: arg1, cnt: 1 };
|
| 1673 |
+
const real = (...args) => {
|
| 1674 |
+
|
| 1675 |
+
// First up with a closure we increment the internal reference
|
| 1676 |
+
// count. This ensures that the Rust closure environment won't
|
| 1677 |
+
// be deallocated while we're invoking it.
|
| 1678 |
+
state.cnt++;
|
| 1679 |
+
const a = state.a;
|
| 1680 |
+
state.a = 0;
|
| 1681 |
+
try {
|
| 1682 |
+
return f(a, state.b, ...args);
|
| 1683 |
+
} finally {
|
| 1684 |
+
state.a = a;
|
| 1685 |
+
real._wbg_cb_unref();
|
| 1686 |
+
}
|
| 1687 |
+
};
|
| 1688 |
+
real._wbg_cb_unref = () => {
|
| 1689 |
+
if (--state.cnt === 0) {
|
| 1690 |
+
wasm.__wbindgen_destroy_closure(state.a, state.b);
|
| 1691 |
+
state.a = 0;
|
| 1692 |
+
CLOSURE_DTORS.unregister(state);
|
| 1693 |
+
}
|
| 1694 |
+
};
|
| 1695 |
+
CLOSURE_DTORS.register(real, state, state);
|
| 1696 |
+
return real;
|
| 1697 |
+
}
|
| 1698 |
+
|
| 1699 |
+
function passArray8ToWasm0(arg, malloc) {
|
| 1700 |
+
const ptr = malloc(arg.length * 1, 1) >>> 0;
|
| 1701 |
+
getUint8ArrayMemory0().set(arg, ptr / 1);
|
| 1702 |
+
WASM_VECTOR_LEN = arg.length;
|
| 1703 |
+
return ptr;
|
| 1704 |
+
}
|
| 1705 |
+
|
| 1706 |
+
function passStringToWasm0(arg, malloc, realloc) {
|
| 1707 |
+
if (realloc === undefined) {
|
| 1708 |
+
const buf = cachedTextEncoder.encode(arg);
|
| 1709 |
+
const ptr = malloc(buf.length, 1) >>> 0;
|
| 1710 |
+
getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
|
| 1711 |
+
WASM_VECTOR_LEN = buf.length;
|
| 1712 |
+
return ptr;
|
| 1713 |
+
}
|
| 1714 |
+
|
| 1715 |
+
let len = arg.length;
|
| 1716 |
+
let ptr = malloc(len, 1) >>> 0;
|
| 1717 |
+
|
| 1718 |
+
const mem = getUint8ArrayMemory0();
|
| 1719 |
+
|
| 1720 |
+
let offset = 0;
|
| 1721 |
+
|
| 1722 |
+
for (; offset < len; offset++) {
|
| 1723 |
+
const code = arg.charCodeAt(offset);
|
| 1724 |
+
if (code > 0x7F) break;
|
| 1725 |
+
mem[ptr + offset] = code;
|
| 1726 |
+
}
|
| 1727 |
+
if (offset !== len) {
|
| 1728 |
+
if (offset !== 0) {
|
| 1729 |
+
arg = arg.slice(offset);
|
| 1730 |
+
}
|
| 1731 |
+
ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
|
| 1732 |
+
const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
|
| 1733 |
+
const ret = cachedTextEncoder.encodeInto(arg, view);
|
| 1734 |
+
|
| 1735 |
+
offset += ret.written;
|
| 1736 |
+
ptr = realloc(ptr, len, offset, 1) >>> 0;
|
| 1737 |
+
}
|
| 1738 |
+
|
| 1739 |
+
WASM_VECTOR_LEN = offset;
|
| 1740 |
+
return ptr;
|
| 1741 |
+
}
|
| 1742 |
+
|
| 1743 |
+
function takeFromExternrefTable0(idx) {
|
| 1744 |
+
const value = wasm.__wbindgen_externrefs.get(idx);
|
| 1745 |
+
wasm.__externref_table_dealloc(idx);
|
| 1746 |
+
return value;
|
| 1747 |
+
}
|
| 1748 |
+
|
| 1749 |
+
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
| 1750 |
+
cachedTextDecoder.decode();
|
| 1751 |
+
const MAX_SAFARI_DECODE_BYTES = 2146435072;
|
| 1752 |
+
let numBytesDecoded = 0;
|
| 1753 |
+
function decodeText(ptr, len) {
|
| 1754 |
+
numBytesDecoded += len;
|
| 1755 |
+
if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
|
| 1756 |
+
cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
|
| 1757 |
+
cachedTextDecoder.decode();
|
| 1758 |
+
numBytesDecoded = len;
|
| 1759 |
+
}
|
| 1760 |
+
return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
|
| 1761 |
+
}
|
| 1762 |
+
|
| 1763 |
+
const cachedTextEncoder = new TextEncoder();
|
| 1764 |
+
|
| 1765 |
+
if (!('encodeInto' in cachedTextEncoder)) {
|
| 1766 |
+
cachedTextEncoder.encodeInto = function (arg, view) {
|
| 1767 |
+
const buf = cachedTextEncoder.encode(arg);
|
| 1768 |
+
view.set(buf);
|
| 1769 |
+
return {
|
| 1770 |
+
read: arg.length,
|
| 1771 |
+
written: buf.length
|
| 1772 |
+
};
|
| 1773 |
+
};
|
| 1774 |
+
}
|
| 1775 |
+
|
| 1776 |
+
let WASM_VECTOR_LEN = 0;
|
| 1777 |
+
|
| 1778 |
+
let wasmModule, wasmInstance, wasm;
|
| 1779 |
+
function __wbg_finalize_init(instance, module) {
|
| 1780 |
+
wasmInstance = instance;
|
| 1781 |
+
wasm = instance.exports;
|
| 1782 |
+
wasmModule = module;
|
| 1783 |
+
cachedDataViewMemory0 = null;
|
| 1784 |
+
cachedUint8ArrayMemory0 = null;
|
| 1785 |
+
wasm.__wbindgen_start();
|
| 1786 |
+
return wasm;
|
| 1787 |
+
}
|
| 1788 |
+
|
| 1789 |
+
async function __wbg_load(module, imports) {
|
| 1790 |
+
if (typeof Response === 'function' && module instanceof Response) {
|
| 1791 |
+
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
| 1792 |
+
try {
|
| 1793 |
+
return await WebAssembly.instantiateStreaming(module, imports);
|
| 1794 |
+
} catch (e) {
|
| 1795 |
+
const validResponse = module.ok && expectedResponseType(module.type);
|
| 1796 |
+
|
| 1797 |
+
if (validResponse && module.headers.get('Content-Type') !== 'application/wasm') {
|
| 1798 |
+
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve Wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
|
| 1799 |
+
|
| 1800 |
+
} else { throw e; }
|
| 1801 |
+
}
|
| 1802 |
+
}
|
| 1803 |
+
|
| 1804 |
+
const bytes = await module.arrayBuffer();
|
| 1805 |
+
return await WebAssembly.instantiate(bytes, imports);
|
| 1806 |
+
} else {
|
| 1807 |
+
const instance = await WebAssembly.instantiate(module, imports);
|
| 1808 |
+
|
| 1809 |
+
if (instance instanceof WebAssembly.Instance) {
|
| 1810 |
+
return { instance, module };
|
| 1811 |
+
} else {
|
| 1812 |
+
return instance;
|
| 1813 |
+
}
|
| 1814 |
+
}
|
| 1815 |
+
|
| 1816 |
+
function expectedResponseType(type) {
|
| 1817 |
+
switch (type) {
|
| 1818 |
+
case 'basic': case 'cors': case 'default': return true;
|
| 1819 |
+
}
|
| 1820 |
+
return false;
|
| 1821 |
+
}
|
| 1822 |
+
}
|
| 1823 |
+
|
| 1824 |
+
function initSync(module) {
|
| 1825 |
+
if (wasm !== undefined) return wasm;
|
| 1826 |
+
|
| 1827 |
+
|
| 1828 |
+
if (module !== undefined) {
|
| 1829 |
+
if (Object.getPrototypeOf(module) === Object.prototype) {
|
| 1830 |
+
({module} = module)
|
| 1831 |
+
} else {
|
| 1832 |
+
console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
|
| 1833 |
+
}
|
| 1834 |
+
}
|
| 1835 |
+
|
| 1836 |
+
const imports = __wbg_get_imports();
|
| 1837 |
+
if (!(module instanceof WebAssembly.Module)) {
|
| 1838 |
+
module = new WebAssembly.Module(module);
|
| 1839 |
+
}
|
| 1840 |
+
const instance = new WebAssembly.Instance(module, imports);
|
| 1841 |
+
return __wbg_finalize_init(instance, module);
|
| 1842 |
+
}
|
| 1843 |
+
|
| 1844 |
+
async function __wbg_init(module_or_path) {
|
| 1845 |
+
if (wasm !== undefined) return wasm;
|
| 1846 |
+
|
| 1847 |
+
|
| 1848 |
+
if (module_or_path !== undefined) {
|
| 1849 |
+
if (Object.getPrototypeOf(module_or_path) === Object.prototype) {
|
| 1850 |
+
({module_or_path} = module_or_path)
|
| 1851 |
+
} else {
|
| 1852 |
+
console.warn('using deprecated parameters for the initialization function; pass a single object instead')
|
| 1853 |
+
}
|
| 1854 |
+
}
|
| 1855 |
+
|
| 1856 |
+
if (module_or_path === undefined) {
|
| 1857 |
+
module_or_path = new URL('holospaces_web_bg.wasm', import.meta.url);
|
| 1858 |
+
}
|
| 1859 |
+
const imports = __wbg_get_imports();
|
| 1860 |
+
|
| 1861 |
+
if (typeof module_or_path === 'string' || (typeof Request === 'function' && module_or_path instanceof Request) || (typeof URL === 'function' && module_or_path instanceof URL)) {
|
| 1862 |
+
module_or_path = fetch(module_or_path);
|
| 1863 |
+
}
|
| 1864 |
+
|
| 1865 |
+
const { instance, module } = await __wbg_load(await module_or_path, imports);
|
| 1866 |
+
|
| 1867 |
+
return __wbg_finalize_init(instance, module);
|
| 1868 |
+
}
|
| 1869 |
+
|
| 1870 |
+
export { initSync, __wbg_init as default };
|
b/8d34a1e9297fbaf472886031a90d3a06dfd4aa71c0621e4325e0112ef96fb07d
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// messenger-skin.mjs — bakes the SLACK re-tone as the messenger default (aubergine chrome + a floating,
|
| 2 |
+
// inset content pane; see the data-skin="slack" block in messenger-skins) and applies the hi-DPI /
|
| 3 |
+
// high-FPS perf layer. No picker / no toggle. Additive + guarded. Escape hatch: set localStorage
|
| 4 |
+
// "holo.skin" to graphite | warm | contrast | light (or "" to remove) to override the default.
|
| 5 |
+
|
| 6 |
+
(function () {
|
| 7 |
+
if (!document.querySelector("link[data-holo-skins]")) {
|
| 8 |
+
const l = document.createElement("link"); l.rel = "stylesheet"; l.href = new URL("./messenger-skins.css?v=7d883ae6cf3e", import.meta.url); l.setAttribute("data-holo-skins", "1"); document.head.appendChild(l);
|
| 9 |
+
}
|
| 10 |
+
let skin = "slack";
|
| 11 |
+
try { const s = localStorage.getItem("holo.skin"); if (s) skin = s; } catch {}
|
| 12 |
+
if (skin) document.documentElement.setAttribute("data-skin", skin); else document.documentElement.removeAttribute("data-skin");
|
| 13 |
+
// remove any lingering picker + its persisted flag from earlier ?skins=1 sessions
|
| 14 |
+
try { localStorage.removeItem("holo.skins"); } catch {}
|
| 15 |
+
try { const p = document.querySelector(".holo-skin-pick"); if (p) p.remove(); } catch {}
|
| 16 |
+
})();
|
b/9d90b4ea8bccee8c239073c7ec7dd5158c6d11f1f668487570d17475d79e661e
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:9d90b4ea8bccee8c239073c7ec7dd5158c6d11f1f668487570d17475d79e661e
|
| 3 |
+
size 6469596
|
b/a2276a0af47a3bc0c9c83d8d370bcc85c07e9c0f516b196ac158e575d5277b02
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{a as h,b as x,c as C,d as v}from"./chunk-VJZ6ZLRJ.js";import"./chunk-SQE76S5B.js";function y(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function E(e,{claimSecret:n,signer:r,at:a}){let i={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:n,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:r.pub,at:a};return i.sig=await r.sign(y(i)),i.tid=await h(y(i)),i}async function M(e,{signer:n,at:r}){let a={type:"refund",kappa:e.kappa,expires:e.expires,by:n.pub,at:r};return a.sig=await n.sign(y(a)),a.tid=await h(y(a)),a}async function _(e,n){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await C(e.by,e.sig,y(e))?!1:e.type==="claim"?!(!e.preimage||await h(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&n>e.expires):!1}async function S(e,n=Date.now()){let r=[];for(let o of e)await _(o,n)&&r.push(o);let a=r.filter(o=>o.type==="claim");if(a.length){a.sort((t,s)=>t.at-s.at||(t.tid<s.tid?-1:t.tid>s.tid?1:0));let o=a[0];return{state:"claimed",winner:o.tid,by:o.by,amount:o.amount,asset:o.asset,fiat:o.fiat}}let i=r.filter(o=>o.type==="refund");return i.length?(i.sort((o,t)=>o.at-t.at||(o.tid<t.tid?-1:1)),{state:"refunded",winner:i[0].tid}):{state:"open"}}function P(e){return e&&e.transitions?Object.values(e.transitions):[]}async function R(e,n,r=Date.now()){e.transitions=e.transitions||{};for(let a of n||[])e.transitions[a.tid]||await _(a,r)&&(e.transitions[a.tid]=a);return e}var D=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),b=new Map;function d(){return{get:e=>b.get(e)||null,set:(e,n)=>b.set(e,n),has:e=>b.has(e)}}function L(e="holo-escrow"){if(typeof localStorage>"u")return d();let n=r=>e+":"+r;return{get:r=>{try{let a=localStorage.getItem(n(r));return a?JSON.parse(a):null}catch{return null}},set:(r,a)=>{try{localStorage.setItem(n(r),JSON.stringify(a))}catch{}},has:r=>{try{return localStorage.getItem(n(r))!=null}catch{return!1}}}}async function q(e,{store:n=null,wallet:r=null,chain:a="base",nowMs:i=null}={}){let o=n||d();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await v(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let t=i||Date.now();if(e.expires&&t>e.expires)return{ok:!1,error:"already expired"};let s=o.get(e.kappa);if(s&&s.funded)return{ok:!0,kappa:e.kappa,mode:s.mode,live:s.mode==="live",tx:s.tx,already:!0};let f=r&&typeof r.htlcFund=="function"?"live":"custodial",u=null,m=null;if(f==="live"){let l;try{l=await r.htlcFund({chain:a,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(k){return{ok:!1,error:String(k&&k.message||k)}}u=l&&(l.tx||l.hash)||null,m=l&&l.swapId||e.hashlock}let c=s||D();return c.funded=!0,c.mode=f,c.tx=u,c.swapId=m,c.chain=f==="live"?a:null,c.amount=e.amount,c.asset=e.asset,c.fiat=e.fiat||null,c.expires=e.expires,o.set(e.kappa,c),{ok:!0,kappa:e.kappa,mode:f,live:f==="live",tx:u,swapId:m}}async function z(e,{claimSecret:n=null,claimerPub:r=null,claimerSigner:a=null,store:i=null,nowMs:o=null,wallet:t=null}={}){let s=i||d(),f=o||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await v(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&f>e.expires)return{ok:!1,error:"this payment has expired"};let u=n||e.claimSecret;if(!u)return{ok:!1,error:"this link has no claim key"};if(await h(u)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let m=a||await x(),c=await E(e,{claimSecret:u,signer:m,at:f}),l=s.get(e.kappa)||D();l.funded||(l.offDevice=!0);let k=l.committed&&l.committed!==c.tid;if(l.transitions[c.tid]=c,l.committed||(l.committed=c.tid),s.set(e.kappa,l),k)return{ok:!1,error:"already claimed"};let j=null,g=l.mode==="live";if(t&&typeof t.htlcClaim=="function")try{let p=g?{chain:l.chain}:typeof t.htlcSwapExists=="function"?await t.htlcSwapExists({hashlock:e.hashlock}):null;if(p){g=!0,l.mode="live",l.chain=l.chain||p.chain||null,s.set(e.kappa,l);let w=await t.htlcClaim({chain:l.chain||p.chain,hashlock:e.hashlock,preimage:u});j=w&&(w.tx||w.hash)||null}}catch(p){return{ok:!1,error:"on-chain claim failed: "+String(p&&p.message||p)}}return{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:l.mode,live:g,offDevice:!!l.offDevice,tid:c.tid,tx:j,claimerPub:r||m.pub,custodialWallet:g?null:"\u03BA:"+e.kappa.slice(0,16)}}async function A(e,{store:n=null,signer:r=null,nowMs:a=null}={}){let i=n||d(),o=a||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let t=i.get(e.kappa);if(!t)return{ok:!1,error:"no escrow to refund"};let s=await S(Object.values(t.transitions),o);if(s.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(s.state==="refunded")return{ok:!1,error:"already refunded"};if(!(t.expires&&o>t.expires))return{ok:!1,error:"not yet expired, can't refund"};let f=r||await x(),u=await M(e,{signer:f,at:o});return t.transitions[u.tid]=u,i.set(e.kappa,t),{ok:!0,amount:t.amount,asset:t.asset,mode:t.mode}}async function T(e,{store:n=null,nowMs:r=null}={}){let a=n||d(),i=r||Date.now(),o=typeof e=="string"?e:e&&e.kappa;if(!o)return{state:"none"};let t=a.get(o);if(!t)return{state:"none"};let s=await S(Object.values(t.transitions),i);return s.state==="open"?{state:t.funded?"open":"none",committed:t.committed||null,canonicalWinner:null,reconciled:!1}:{state:s.state,canonicalWinner:s.winner,committed:t.committed||null,reconciled:!!(t.committed&&s.state==="claimed"&&t.committed!==s.winner),by:s.by,amount:s.amount,asset:s.asset}}function B(e,{store:n=null}={}){let r=n||d(),a=typeof e=="string"?e:e&&e.kappa,i=r.get(a);return i?P(i):[]}async function G(e,n,{store:r=null,nowMs:a=null}={}){let i=r||d(),o=a||Date.now(),t=typeof e=="string"?e:e&&e.kappa,s=i.get(t)||D();return await R(s,n||[],o),i.set(t,s),T(t,{store:i,nowMs:o})}export{z as claimEscrow,B as exportPeer,q as fund,L as localStore,G as mergePeer,A as refund,T as status};
|
b/a5b5d9c559a1d2812c33e9d7a4b1c24bb03059482503a23356e4c9c32d4d8b88
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
b/b9f8a887de588c5193dc65ca9ce206a70c6df53595b848e0ce0299c2d88a5753
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
b/c9a284fdd54f5472ac7d7781d9ac2d289f7dbacb9c710b0315bee75cd68ed99e
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// holo-release-verify.mjs — downstream acceptance of a release pointer (S2/S3 of the downstream-holospace
|
| 2 |
+
// initiative). Isomorphic: the boot page, the service worker, and Node witnesses all use THIS one module.
|
| 3 |
+
//
|
| 4 |
+
// The rule (fail-closed, Law L5 + SEC-1/4/6):
|
| 5 |
+
// a head is accepted only if (1) its id re-derives from its body, (2) it is SIGNED and the signature
|
| 6 |
+
// verifies over the id, (3) authorship is CONTINUOUS with the pinned head (same pub key), and
|
| 7 |
+
// (4) it does not travel backwards (seq strictly grows; the same head re-presented is a no-op).
|
| 8 |
+
// With no pin yet (first boot) the head is trusted-on-first-use and becomes the pin — every later
|
| 9 |
+
// update must chain from it. Rollback is NOT a violation: re-pinning a parent is an explicit local
|
| 10 |
+
// act (the store still holds it), never something a remote pointer can cause.
|
| 11 |
+
|
| 12 |
+
import { verifyEntry } from "/usr/lib/holo/holo-strand.mjs";
|
| 13 |
+
|
| 14 |
+
// verifyChain(entries) — Law L5 over the whole strand: every entry re-derives + verifies its
|
| 15 |
+
// signature, seq is dense from 0, every prev is exactly the prior id. Mirrors strand.verify()
|
| 16 |
+
// for a plain array (the pointer file world has no backend).
|
| 17 |
+
export async function verifyChain(entries) {
|
| 18 |
+
let prev = null;
|
| 19 |
+
for (let i = 0; i < (entries || []).length; i++) {
|
| 20 |
+
const rec = entries[i];
|
| 21 |
+
const v = await verifyEntry(rec);
|
| 22 |
+
if (!v.ok) return { ok: false, brokeAt: i, why: v.why };
|
| 23 |
+
if (rec["holstr:seq"] !== i) return { ok: false, brokeAt: i, why: "seq-out-of-order" };
|
| 24 |
+
if (rec["holstr:prev"] !== prev) return { ok: false, brokeAt: i, why: "prev-link-broken" };
|
| 25 |
+
prev = rec.id;
|
| 26 |
+
}
|
| 27 |
+
return { ok: true, length: (entries || []).length, head: prev };
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
// acceptHead(head, pinned) → { ok, pin?, why?, unchanged? }
|
| 31 |
+
// head : the fetched release.json record (untrusted bytes from an untrusted origin)
|
| 32 |
+
// pinned : the locally durable { id, seq, pub } from the last accepted release (null on first boot)
|
| 33 |
+
export async function acceptHead(head, pinned = null) {
|
| 34 |
+
if (!head || typeof head !== "object") return { ok: false, why: "no-head" };
|
| 35 |
+
const v = await verifyEntry(head);
|
| 36 |
+
if (!v.ok) return { ok: false, why: v.why }; // tampered or forged (L5/SEC-1)
|
| 37 |
+
if (!v.signed) return { ok: false, why: "unsigned-release" }; // no signature → no update
|
| 38 |
+
const pub = head["holstr:pub"], seq = head["holstr:seq"];
|
| 39 |
+
if (pinned) {
|
| 40 |
+
if (pub !== pinned.pub) return { ok: false, why: "key-discontinuity" }; // a different author cannot replace the strand (SEC-4)
|
| 41 |
+
if (head.id === pinned.id) return { ok: true, unchanged: true, pin: pinned };
|
| 42 |
+
if (!(seq > pinned.seq)) return { ok: false, why: "replay-stale-head" }; // an old release re-presented as new is refused
|
| 43 |
+
}
|
| 44 |
+
return { ok: true, pin: { id: head.id, seq, pub } };
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
export default { verifyChain, acceptHead };
|
b/d1fdc38c3e96ab2c5c176a4e016516b27197ec0f8dde9467532e75703cacaa7e
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
b/d2e8a9ef211a9cf3d441297f50e5ef6949d8a7c43ebbe56705e75eb32e969937
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{a as h,b as x,c as j,d as v}from"./chunk-VJZ6ZLRJ.js";import"./chunk-SQE76S5B.js";function y(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function C(e,{claimSecret:i,signer:r,at:a}){let l={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:i,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:r.pub,at:a};return l.sig=await r.sign(y(l)),l.tid=await h(y(l)),l}async function E(e,{signer:i,at:r}){let a={type:"refund",kappa:e.kappa,expires:e.expires,by:i.pub,at:r};return a.sig=await i.sign(y(a)),a.tid=await h(y(a)),a}async function M(e,i){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await j(e.by,e.sig,y(e))?!1:e.type==="claim"?!(!e.preimage||await h(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&i>e.expires):!1}async function S(e,i=Date.now()){let r=[];for(let n of e)await M(n,i)&&r.push(n);let a=r.filter(n=>n.type==="claim");if(a.length){a.sort((s,t)=>s.at-t.at||(s.tid<t.tid?-1:s.tid>t.tid?1:0));let n=a[0];return{state:"claimed",winner:n.tid,by:n.by,amount:n.amount,asset:n.asset,fiat:n.fiat}}let l=r.filter(n=>n.type==="refund");return l.length?(l.sort((n,s)=>n.at-s.at||(n.tid<s.tid?-1:1)),{state:"refunded",winner:l[0].tid}):{state:"open"}}function _(e){return e&&e.transitions?Object.values(e.transitions):[]}async function P(e,i,r=Date.now()){e.transitions=e.transitions||{};for(let a of i||[])e.transitions[a.tid]||await M(a,r)&&(e.transitions[a.tid]=a);return e}var D=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),b=new Map;function m(){return{get:e=>b.get(e)||null,set:(e,i)=>b.set(e,i),has:e=>b.has(e)}}function L(e="holo-escrow"){if(typeof localStorage>"u")return m();let i=r=>e+":"+r;return{get:r=>{try{let a=localStorage.getItem(i(r));return a?JSON.parse(a):null}catch{return null}},set:(r,a)=>{try{localStorage.setItem(i(r),JSON.stringify(a))}catch{}},has:r=>{try{return localStorage.getItem(i(r))!=null}catch{return!1}}}}async function q(e,{store:i=null,wallet:r=null,chain:a="base",nowMs:l=null}={}){let n=i||m();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await v(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let s=l||Date.now();if(e.expires&&s>e.expires)return{ok:!1,error:"already expired"};let t=n.get(e.kappa);if(t&&t.funded)return{ok:!0,kappa:e.kappa,mode:t.mode,live:t.mode==="live",tx:t.tx,already:!0};let u=r&&typeof r.htlcFund=="function"?"live":"custodial",f=null,p=null;if(u==="live"){let o;try{o=await r.htlcFund({chain:a,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(k){return{ok:!1,error:String(k&&k.message||k)}}f=o&&(o.tx||o.hash)||null,p=o&&o.swapId||e.hashlock}let c=t||D();return c.funded=!0,c.mode=u,c.tx=f,c.swapId=p,c.chain=u==="live"?a:null,c.amount=e.amount,c.asset=e.asset,c.fiat=e.fiat||null,c.expires=e.expires,n.set(e.kappa,c),{ok:!0,kappa:e.kappa,mode:u,live:u==="live",tx:f,swapId:p}}async function z(e,{claimSecret:i=null,claimerPub:r=null,claimerSigner:a=null,store:l=null,nowMs:n=null,wallet:s=null}={}){let t=l||m(),u=n||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await v(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&u>e.expires)return{ok:!1,error:"this payment has expired"};let f=i||e.claimSecret;if(!f)return{ok:!1,error:"this link has no claim key"};if(await h(f)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let p=a||await x(),c=await C(e,{claimSecret:f,signer:p,at:u}),o=t.get(e.kappa)||D();o.funded||(o.offDevice=!0);let k=o.committed&&o.committed!==c.tid;if(o.transitions[c.tid]=c,o.committed||(o.committed=c.tid),t.set(e.kappa,o),k)return{ok:!1,error:"already claimed"};let R=null,g=o.mode==="live";if(s&&typeof s.htlcClaim=="function")try{let d=g?{chain:o.chain}:typeof s.htlcSwapExists=="function"?await s.htlcSwapExists({hashlock:e.hashlock}):null;if(d){g=!0,o.mode="live",o.chain=o.chain||d.chain||null,t.set(e.kappa,o);let w=await s.htlcClaim({chain:o.chain||d.chain,hashlock:e.hashlock,preimage:f});R=w&&(w.tx||w.hash)||null}}catch(d){return{ok:!1,error:"on-chain claim failed: "+String(d&&d.message||d)}}return{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:o.mode,live:g,offDevice:!!o.offDevice,tid:c.tid,tx:R,claimerPub:r||p.pub,custodialWallet:g?null:"\u03BA:"+e.kappa.slice(0,16)}}async function A(e,{store:i=null,signer:r=null,nowMs:a=null,wallet:l=null}={}){let n=i||m(),s=a||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let t=n.get(e.kappa);if(!t)return{ok:!1,error:"no escrow to refund"};let u=await S(Object.values(t.transitions),s);if(u.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(u.state==="refunded")return{ok:!1,error:"already refunded"};if(!(t.expires&&s>t.expires))return{ok:!1,error:"not yet expired, can't refund"};let f=r||await x(),p=await E(e,{signer:f,at:s});t.transitions[p.tid]=p,n.set(e.kappa,t);let c=null;if(t.mode==="live"&&l&&typeof l.htlcRefund=="function")try{let o=await l.htlcRefund({chain:t.chain,hashlock:e.hashlock});c=o&&(o.tx||o.hash)||null}catch(o){return{ok:!1,error:"on-chain refund failed: "+String(o&&o.message||o)}}return{ok:!0,amount:t.amount,asset:t.asset,mode:t.mode,live:t.mode==="live",tx:c}}async function T(e,{store:i=null,nowMs:r=null}={}){let a=i||m(),l=r||Date.now(),n=typeof e=="string"?e:e&&e.kappa;if(!n)return{state:"none"};let s=a.get(n);if(!s)return{state:"none"};let t=await S(Object.values(s.transitions),l);return t.state==="open"?{state:s.funded?"open":"none",committed:s.committed||null,canonicalWinner:null,reconciled:!1}:{state:t.state,canonicalWinner:t.winner,committed:s.committed||null,reconciled:!!(s.committed&&t.state==="claimed"&&s.committed!==t.winner),by:t.by,amount:t.amount,asset:t.asset}}function B(e,{store:i=null}={}){let r=i||m(),a=typeof e=="string"?e:e&&e.kappa,l=r.get(a);return l?_(l):[]}async function G(e,i,{store:r=null,nowMs:a=null}={}){let l=r||m(),n=a||Date.now(),s=typeof e=="string"?e:e&&e.kappa,t=l.get(s)||D();return await P(t,i||[],n),l.set(s,t),T(s,{store:l,nowMs:n})}export{z as claimEscrow,B as exportPeer,q as fund,L as localStore,G as mergePeer,A as refund,T as status};
|
b/e0b2a13d7c3890a4ee3fc6120d2b42a3901ba068897aca8ce2080320f63446ee
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
b/e7b914a4e11aedb2e0239e7a84502a8241f2fff157d980741728810a1559ff41
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
function l(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),n=decodeURIComponent(escape(atob(t)));return JSON.parse(n)}var i=["v","kind","amount","asset","fiat","to","toName","from","fromName","memo","created","expires","claimSecret","pub","sig"];function u(e){let t=l(e);if(!Array.isArray(t))return t;let n={};return i.forEach((a,o)=>{t[o]!=null&&(n[a]=t[o])}),n.memo==null&&(n.memo=""),n}async function p(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",t);return[...new Uint8Array(n)].map(a=>a.toString(16).padStart(2,"0")).join("")}var w=p;function r(e){let t="";for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function s(e){let t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),n=new Uint8Array(t.length);for(let a=0;a<t.length;a++)n[a]=t.charCodeAt(a);return n}async function y(){let e=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!0,["sign","verify"]),t=new Uint8Array(await crypto.subtle.exportKey("raw",e.publicKey));return{pub:r(t),sign:async n=>r(new Uint8Array(await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},e.privateKey,new TextEncoder().encode(n))))}}async function c(e,t,n){try{let a=await crypto.subtle.importKey("raw",s(e),{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},a,s(t),new TextEncoder().encode(n))}catch{return!1}}var h=c;async function b(e){return!e||!e.pub||!e.sig?!1:c(e.pub,e.sig,d(e))}function d(e){let t=[e.v,e.kind,e.amount,e.asset,e.fiat||"",e.to||"",e.toName||"",e.from||"",e.fromName||"",e.memo||"",e.created||"",e.expires||""];return(e.hashlock||e.pub)&&t.push(e.hashlock||"",e.pub||""),t.join("|")}var f={USD:"$",EUR:"\u20AC",GBP:"\xA3"};function g(e){let t=Number(e.amount);return e.fiat?`${f[e.fiat]||""}${t.toFixed(2)}`:`${t%1===0?t:t.toFixed(4).replace(/0+$/,"")} ${e.asset}`}var m=/(https?:\/\/\S*pay-claim\.html#([A-Za-z0-9_-]+))|(holo:\/\/pay\/\S*#([A-Za-z0-9_-]+))/;function S(e){let t=String(e||"").match(m);if(!t)return null;let n=t[2]||t[4],a=t[0];try{let o=u(n);if(o&&o.v===1&&Number(o.amount)>0)return{intent:o,url:a,payload:n}}catch{}return null}export{w as a,y as b,h as c,b as d,g as e,S as f};
|
b/f011367faa1643ec7403b71b5e585c7a85ede5bd4a08e8ad5e95c31bcdf1161a
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
function l(e){let t=e.replace(/-/g,"+").replace(/_/g,"/"),n=decodeURIComponent(escape(atob(t)));return JSON.parse(n)}var i=["v","kind","amount","asset","fiat","to","toName","from","fromName","memo","created","expires","claimSecret","pub","sig"];function u(e){let t=l(e);if(!Array.isArray(t))return t;let n={};return i.forEach((a,o)=>{t[o]!=null&&(n[a]=t[o])}),n.memo==null&&(n.memo=""),n}async function d(e){let t=new TextEncoder().encode(e),n=await crypto.subtle.digest("SHA-256",t);return[...new Uint8Array(n)].map(a=>a.toString(16).padStart(2,"0")).join("")}var w=d;function r(e){let t="";for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}function c(e){let t=atob(e.replace(/-/g,"+").replace(/_/g,"/")),n=new Uint8Array(t.length);for(let a=0;a<t.length;a++)n[a]=t.charCodeAt(a);return n}async function m(){let e=await crypto.subtle.generateKey({name:"ECDSA",namedCurve:"P-256"},!0,["sign","verify"]),t=new Uint8Array(await crypto.subtle.exportKey("raw",e.publicKey));return{pub:r(t),sign:async n=>r(new Uint8Array(await crypto.subtle.sign({name:"ECDSA",hash:"SHA-256"},e.privateKey,new TextEncoder().encode(n))))}}async function s(e,t,n){try{let a=await crypto.subtle.importKey("raw",c(e),{name:"ECDSA",namedCurve:"P-256"},!1,["verify"]);return await crypto.subtle.verify({name:"ECDSA",hash:"SHA-256"},a,c(t),new TextEncoder().encode(n))}catch{return!1}}var y=s;async function b(e){return!e||!e.pub||!e.sig?!1:s(e.pub,e.sig,p(e))}function p(e){let t=[e.v,e.kind,e.amount,e.asset,e.fiat||"",e.to||"",e.toName||"",e.from||"",e.fromName||"",e.memo||"",e.created||"",e.expires||""];return(e.hashlock||e.pub)&&t.push(e.hashlock||"",e.pub||""),t.join("|")}var f={USD:"$",EUR:"\u20AC",GBP:"\xA3"};function k(e){let t=Number(e.amount);return e.fiat?`${f[e.fiat]||""}${t.toFixed(2)}`:`${t%1===0?t:t.toFixed(4).replace(/0+$/,"")} ${e.asset}`}var h=/(https?:\/\/\S*pay-claim\.html#([A-Za-z0-9_-]+))|(holo:\/\/pay\/\S*#([A-Za-z0-9_-]+))/;function g(e){let t=String(e||"").match(h);if(!t)return null;let n=t[2]||t[4],a=t[0];try{let o=u(n);if(o&&o.v===1&&Number(o.amount)>0)return{intent:o,url:a,payload:n}}catch{}return null}export{w as a,m as b,y as c,b as d,k as e,g as f};
|
b/f4b80dc5a1ba9ad776b7b3793b5b9af2ed35592e45ce447eab9d571d5bc47693
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
import{a as d,b as y,c as w,d as k}from"./chunk-VJZ6ZLRJ.js";import"./chunk-SQE76S5B.js";function m(e){return["L1",e.type,e.kappa,e.hashlock||"",e.preimage||"",String(e.amount??""),e.asset||"",e.fiat||"",String(e.expires||""),e.by||"",String(e.at||"")].join("|")}async function v(e,{claimSecret:s,signer:a,at:t}){let i={type:"claim",kappa:e.kappa,hashlock:e.hashlock,preimage:s,amount:e.amount,asset:e.asset,fiat:e.fiat||null,expires:e.expires,by:a.pub,at:t};return i.sig=await a.sign(m(i)),i.tid=await d(m(i)),i}async function S(e,{signer:s,at:a}){let t={type:"refund",kappa:e.kappa,expires:e.expires,by:s.pub,at:a};return t.sig=await s.sign(m(t)),t.tid=await d(m(t)),t}async function b(e,s){return!e||!e.type||!e.kappa||!e.by||!e.sig||!await w(e.by,e.sig,m(e))?!1:e.type==="claim"?!(!e.preimage||await d(e.preimage)!==e.hashlock||e.expires&&e.at>e.expires):e.type==="refund"?!!(e.expires&&s>e.expires):!1}async function g(e,s=Date.now()){let a=[];for(let o of e)await b(o,s)&&a.push(o);let t=a.filter(o=>o.type==="claim");if(t.length){t.sort((r,n)=>r.at-n.at||(r.tid<n.tid?-1:r.tid>n.tid?1:0));let o=t[0];return{state:"claimed",winner:o.tid,by:o.by,amount:o.amount,asset:o.asset,fiat:o.fiat}}let i=a.filter(o=>o.type==="refund");return i.length?(i.sort((o,r)=>o.at-r.at||(o.tid<r.tid?-1:1)),{state:"refunded",winner:i[0].tid}):{state:"open"}}function D(e){return e&&e.transitions?Object.values(e.transitions):[]}async function j(e,s,a=Date.now()){e.transitions=e.transitions||{};for(let t of s||[])e.transitions[t.tid]||await b(t,a)&&(e.transitions[t.tid]=t);return e}var x=()=>({funded:!1,mode:"custodial",committed:null,transitions:{}}),h=new Map;function p(){return{get:e=>h.get(e)||null,set:(e,s)=>h.set(e,s),has:e=>h.has(e)}}function C(e="holo-escrow"){if(typeof localStorage>"u")return p();let s=a=>e+":"+a;return{get:a=>{try{let t=localStorage.getItem(s(a));return t?JSON.parse(t):null}catch{return null}},set:(a,t)=>{try{localStorage.setItem(s(a),JSON.stringify(t))}catch{}},has:a=>{try{return localStorage.getItem(s(a))!=null}catch{return!1}}}}async function E(e,{store:s=null,wallet:a=null,chain:t="base",nowMs:i=null}={}){let o=s||p();if(!e||!e.kappa)return{ok:!1,error:"no payment to fund"};if(!await k(e))return{ok:!1,error:"this payment isn't authentically signed, refusing to fund"};if(!e.hashlock)return{ok:!1,error:"this payment has no escrow lock"};let r=i||Date.now();if(e.expires&&r>e.expires)return{ok:!1,error:"already expired"};let n=o.get(e.kappa);if(n&&n.funded)return{ok:!0,kappa:e.kappa,mode:n.mode,live:n.mode==="live",tx:n.tx,already:!0};let c=a&&typeof a.htlcFund=="function"?"live":"custodial",f=null;if(c==="live")try{f=await a.htlcFund({chain:t,amount:e.amount,token:e.asset,hashlock:e.hashlock,timeout:e.expires})}catch(l){return{ok:!1,error:String(l&&l.message||l)}}let u=n||x();return u.funded=!0,u.mode=c,u.tx=f,u.amount=e.amount,u.asset=e.asset,u.fiat=e.fiat||null,u.expires=e.expires,o.set(e.kappa,u),{ok:!0,kappa:e.kappa,mode:c,live:c==="live",tx:f}}async function F(e,{claimSecret:s=null,claimerPub:a=null,claimerSigner:t=null,store:i=null,nowMs:o=null}={}){let r=i||p(),n=o||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};if(!await k(e))return{ok:!1,error:"this link isn't authentically signed"};if(e.expires&&n>e.expires)return{ok:!1,error:"this payment has expired"};let c=s||e.claimSecret;if(!c)return{ok:!1,error:"this link has no claim key"};if(await d(c)!==e.hashlock)return{ok:!1,error:"this claim key doesn't match the payment"};let f=t||await y(),u=await v(e,{claimSecret:c,signer:f,at:n}),l=r.get(e.kappa)||x();l.funded||(l.offDevice=!0);let M=l.committed&&l.committed!==u.tid;return l.transitions[u.tid]=u,l.committed||(l.committed=u.tid),r.set(e.kappa,l),M?{ok:!1,error:"already claimed"}:{ok:!0,amount:e.amount,asset:e.asset,fiat:e.fiat||null,mode:l.mode,live:l.mode==="live",offDevice:!!l.offDevice,tid:u.tid,claimerPub:a||f.pub,custodialWallet:"\u03BA:"+e.kappa.slice(0,16)}}async function J(e,{store:s=null,signer:a=null,nowMs:t=null}={}){let i=s||p(),o=t||Date.now();if(!e||!e.kappa)return{ok:!1,error:"not a payment"};let r=i.get(e.kappa);if(!r)return{ok:!1,error:"no escrow to refund"};let n=await g(Object.values(r.transitions),o);if(n.state==="claimed")return{ok:!1,error:"already claimed, can't refund"};if(n.state==="refunded")return{ok:!1,error:"already refunded"};if(!(r.expires&&o>r.expires))return{ok:!1,error:"not yet expired, can't refund"};let c=a||await y(),f=await S(e,{signer:c,at:o});return r.transitions[f.tid]=f,i.set(e.kappa,r),{ok:!0,amount:r.amount,asset:r.asset,mode:r.mode}}async function _(e,{store:s=null,nowMs:a=null}={}){let t=s||p(),i=a||Date.now(),o=typeof e=="string"?e:e&&e.kappa;if(!o)return{state:"none"};let r=t.get(o);if(!r)return{state:"none"};let n=await g(Object.values(r.transitions),i);return n.state==="open"?{state:r.funded?"open":"none",committed:r.committed||null,canonicalWinner:null,reconciled:!1}:{state:n.state,canonicalWinner:n.winner,committed:r.committed||null,reconciled:!!(r.committed&&n.state==="claimed"&&r.committed!==n.winner),by:n.by,amount:n.amount,asset:n.asset}}function N(e,{store:s=null}={}){let a=s||p(),t=typeof e=="string"?e:e&&e.kappa,i=a.get(t);return i?D(i):[]}async function L(e,s,{store:a=null,nowMs:t=null}={}){let i=a||p(),o=t||Date.now(),r=typeof e=="string"?e:e&&e.kappa,n=i.get(r)||x();return await j(n,s||[],o),i.set(r,n),_(r,{store:i,nowMs:o})}export{F as claimEscrow,N as exportPeer,E as fund,C as localStore,L as mergePeer,J as refund,_ as status};
|