@napplet/conformance-cli 0.2.2 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -400,7 +400,6 @@ var NappletConformanceHostBundle = (() => {
400
400
  protocols: options.capabilities?.protocols ?? {}
401
401
  };
402
402
  const services = options.services ?? [];
403
- const klass = options.class ?? null;
404
403
  const records = [];
405
404
  function handle(envelope) {
406
405
  const type = envelope && typeof envelope === "object" && typeof envelope.type === "string" ? envelope.type : void 0;
@@ -409,8 +408,7 @@ var NappletConformanceHostBundle = (() => {
409
408
  {
410
409
  type: "shell.init",
411
410
  capabilities: { domains: capabilities.domains, protocols: capabilities.protocols },
412
- services,
413
- class: klass
411
+ services
414
412
  }
415
413
  ];
416
414
  }
@@ -422,7 +420,6 @@ var NappletConformanceHostBundle = (() => {
422
420
  return {
423
421
  capabilities,
424
422
  services,
425
- class: klass,
426
423
  get records() {
427
424
  return records;
428
425
  },
@@ -1,4 +1,4 @@
1
- (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))o(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const d of r.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&o(d)}).observe(document,{childList:!0,subtree:!0});function s(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(i){if(i.ep)return;i.ep=!0;const r=s(i);fetch(i.href,r)}})();var I=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent"],_=["shell"],a={id:"string"},G={"shell.ready":{dir:"out"},"shell.init":{dir:"in"},"relay.subscribe":{dir:"out",fields:{...a,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...a,subId:"string"}},"relay.publish":{dir:"out",fields:{...a,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...a,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...a,filters:"array"}},"relay.event":{dir:"in"},"relay.eose":{dir:"in"},"relay.closed":{dir:"in"},"relay.publish.result":{dir:"in"},"relay.publishEncrypted.result":{dir:"in"},"relay.query.result":{dir:"in"},"identity.getPublicKey":{dir:"out",fields:{...a}},"identity.getRelays":{dir:"out",fields:{...a}},"identity.getProfile":{dir:"out",fields:{...a}},"identity.getFollows":{dir:"out",fields:{...a}},"identity.getList":{dir:"out",fields:{...a,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...a}},"identity.getMutes":{dir:"out",fields:{...a}},"identity.getBlocked":{dir:"out",fields:{...a}},"identity.getBadges":{dir:"out",fields:{...a}},"identity.changed":{dir:"in"},"identity.getPublicKey.result":{dir:"in"},"identity.getRelays.result":{dir:"in"},"identity.getProfile.result":{dir:"in"},"identity.getFollows.result":{dir:"in"},"identity.getList.result":{dir:"in"},"identity.getZaps.result":{dir:"in"},"identity.getMutes.result":{dir:"in"},"identity.getBlocked.result":{dir:"in"},"identity.getBadges.result":{dir:"in"},"storage.get":{dir:"out",fields:{...a,key:"string"}},"storage.set":{dir:"out",fields:{...a,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...a,key:"string"}},"storage.keys":{dir:"out",fields:{...a}},"storage.get.result":{dir:"in"},"storage.set.result":{dir:"in"},"storage.remove.result":{dir:"in"},"storage.keys.result":{dir:"in"},"inc.emit":{dir:"out",fields:{topic:"string"}},"inc.subscribe":{dir:"out",fields:{...a,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...a,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...a}},"inc.channel.close":{dir:"out",fields:{channelId:"string"}},"inc.subscribe.result":{dir:"in"},"inc.event":{dir:"in"},"inc.channel.open.result":{dir:"in"},"inc.channel.event":{dir:"in"},"inc.channel.list.result":{dir:"in"},"inc.channel.closed":{dir:"in"},"theme.get":{dir:"out",fields:{...a}},"theme.get.result":{dir:"in"},"theme.changed":{dir:"in"},"keys.forward":{dir:"out",fields:{key:"string",code:"string",ctrl:"boolean",alt:"boolean",shift:"boolean",meta:"boolean"}},"keys.registerAction":{dir:"out",fields:{...a,action:"object"}},"keys.unregisterAction":{dir:"out",fields:{actionId:"string"}},"keys.registerAction.result":{dir:"in"},"keys.bindings":{dir:"in"},"keys.action":{dir:"in"},"media.session.create":{dir:"out",fields:{...a,owner:"object"}},"media.session.update":{dir:"out",fields:{sessionId:"string",metadata:"object"}},"media.session.destroy":{dir:"out",fields:{sessionId:"string"}},"media.state":{dir:"out",fields:{sessionId:"string",status:"string"}},"media.capabilities":{dir:"out",fields:{sessionId:"string",actions:"array"}},"media.session.create.result":{dir:"in"},"media.command":{dir:"in"},"media.controls":{dir:"in"},"notify.send":{dir:"out",fields:{...a,title:"string"}},"notify.dismiss":{dir:"out",fields:{notificationId:"string"}},"notify.badge":{dir:"out",fields:{count:"number"}},"notify.channel.register":{dir:"out",fields:{channelId:"string",label:"string"}},"notify.permission.request":{dir:"out",fields:{...a}},"notify.send.result":{dir:"in"},"notify.permission.result":{dir:"in"},"notify.action":{dir:"in"},"notify.clicked":{dir:"in"},"notify.dismissed":{dir:"in"},"notify.controls":{dir:"in"},"config.registerSchema":{dir:"out",fields:{...a,schema:"object"}},"config.get":{dir:"out",fields:{...a}},"config.subscribe":{dir:"out"},"config.unsubscribe":{dir:"out"},"config.openSettings":{dir:"out"},"config.registerSchema.result":{dir:"in"},"config.values":{dir:"in"},"config.schemaError":{dir:"in"},"resource.bytes":{dir:"out",fields:{...a,url:"string"}},"resource.cancel":{dir:"out",fields:{...a}},"resource.bytes.result":{dir:"in"},"resource.bytes.error":{dir:"in"},"cvm.discover":{dir:"out",fields:{...a}},"cvm.request":{dir:"out",fields:{...a,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...a,server:"object"}},"cvm.discover.result":{dir:"in"},"cvm.request.result":{dir:"in"},"cvm.close.result":{dir:"in"},"cvm.event":{dir:"in"},"outbox.query":{dir:"out",fields:{...a,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...a,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...a,subId:"string"}},"outbox.publish":{dir:"out",fields:{...a,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...a,target:"present"}},"outbox.query.result":{dir:"in"},"outbox.event":{dir:"in"},"outbox.eose":{dir:"in"},"outbox.closed":{dir:"in"},"outbox.publish.result":{dir:"in"},"outbox.resolveRelays.result":{dir:"in"},"upload.upload":{dir:"out",fields:{...a,request:"object"}},"upload.status":{dir:"out",fields:{...a,uploadId:"string"}},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...a,request:"object"}},"intent.available":{dir:"out",fields:{...a,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...a}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"}};function B(e){if(e===void 0)return"undefined";if(e===null)return"null";if(Array.isArray(e))return"array";const t=typeof e;return t==="string"||t==="number"||t==="boolean"?t:t==="object"?"object":"present"}function K(e,t){return t==="present"?e!=null:B(e)===t}function U(e){const t=[];if(typeof e!="object"||e===null||Array.isArray(e))return{ok:!1,errors:[{code:"not-an-object",message:"Envelope must be a non-null object"}]};const s=e,o=s.type;if(typeof o!="string")return{ok:!1,errors:[{code:"missing-type",message:"Envelope is missing a string `type` field"}]};const i=o.indexOf(".");if(i<=0)return{ok:!1,type:o,errors:[{code:"malformed-type",message:`Envelope type "${o}" is not in domain.action form`}]};const r=o.slice(0,i);if(!(I.includes(r)||_.includes(r)))return{ok:!1,type:o,domain:r,errors:[{code:"unknown-domain",message:`"${r}" is not a known NAP domain`}]};const c=G[o];if(!c)return{ok:!1,type:o,domain:r,errors:[{code:"unknown-type",message:`"${o}" is not a known ${r} message type`}]};if(c.dir==="in")return{ok:!1,type:o,domain:r,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${o}" is a shell→napplet message; a napplet must not emit it`}]};for(const[n,f]of Object.entries(c.fields??{})){if(!(n in s)||s[n]===void 0){t.push({code:"missing-field",message:`Required field "${n}" is missing`,field:n});continue}K(s[n],f)||t.push({code:"wrong-type",message:`Field "${n}" should be ${f} but is ${B(s[n])}`,field:n})}return{ok:t.length===0,type:o,domain:r,direction:"out",errors:t}}var R=/^[a-z0-9][a-z0-9._:-]*$/;function Z(e){return e.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g,(t,s)=>{if(s[0]==="#"){const i=s[1]==="x"||s[1]==="X"?parseInt(s.slice(2),16):parseInt(s.slice(1),10);return Number.isFinite(i)?String.fromCodePoint(i):t}return{amp:"&",lt:"<",gt:">",quot:'"',apos:"'"}[s]??t})}function S(e,t){const s=/<meta\b[^>]*>/gi;let o;for(;(o=s.exec(e))!==null;){const i=o[0],r=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);if((r?.[1]??r?.[2]??"").trim()!==t)continue;const c=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);return Z((c?.[1]??c?.[2]??"").trim())}}function L(e){if(Array.isArray(e))return e.some(L);if(e&&typeof e=="object"){const t=e;return"pattern"in t?!0:Object.values(t).some(L)}return!1}function k(e,t={}){const s=[],o=[],i=S(e,"napplet-type");i?R.test(i)||s.push({code:"invalid-napplet-type",message:`napplet-type "${i}" must match ${R}`}):s.push({code:"missing-napplet-type",message:'No <meta name="napplet-type"> declared'});const r=S(e,"napplet-requires"),d=r?r.split(",").map(n=>n.trim()).filter(Boolean):[];for(const n of d){const f=n.startsWith("nap:")?n.slice(4):n;I.includes(f)||s.push({code:"unknown-required-nap",message:`napplet-requires lists "${n}" which is not a known NAP domain`})}const c=S(e,"napplet-config-schema");if(c){let n;try{n=JSON.parse(c)}catch{n=void 0,s.push({code:"invalid-config-schema",message:"napplet-config-schema is not valid JSON"})}n!==void 0&&(typeof n!="object"||n===null||Array.isArray(n)?s.push({code:"invalid-config-schema",message:"napplet-config-schema must be a JSON Schema object"}):L(n)&&s.push({code:"invalid-config-schema",message:"napplet-config-schema uses the `pattern` keyword, which the draft-07 core subset excludes (CVE-2025-69873)"}))}return{ok:s.length===0,nappletType:i,requires:d,errors:s,warnings:o}}var z="f".repeat(64),J="https://reference.invalid/blob",l=e=>[e],p=()=>[],V={"relay.subscribe":e=>l({type:"relay.eose",subId:e.subId}),"relay.close":p,"relay.publish":e=>l({type:"relay.publish.result",id:e.id,ok:!0,event:e.event}),"relay.publishEncrypted":e=>l({type:"relay.publishEncrypted.result",id:e.id,ok:!0,event:e.event}),"relay.query":e=>l({type:"relay.query.result",id:e.id,events:[]}),"identity.getPublicKey":e=>l({type:"identity.getPublicKey.result",id:e.id,pubkey:z}),"identity.getRelays":e=>l({type:"identity.getRelays.result",id:e.id,relays:{}}),"identity.getProfile":e=>l({type:"identity.getProfile.result",id:e.id,profile:null}),"identity.getFollows":e=>l({type:"identity.getFollows.result",id:e.id,pubkeys:[]}),"identity.getList":e=>l({type:"identity.getList.result",id:e.id,entries:[]}),"identity.getZaps":e=>l({type:"identity.getZaps.result",id:e.id,zaps:[]}),"identity.getMutes":e=>l({type:"identity.getMutes.result",id:e.id,pubkeys:[]}),"identity.getBlocked":e=>l({type:"identity.getBlocked.result",id:e.id,pubkeys:[]}),"identity.getBadges":e=>l({type:"identity.getBadges.result",id:e.id,badges:[]}),"storage.get":e=>l({type:"storage.get.result",id:e.id,value:null}),"storage.set":e=>l({type:"storage.set.result",id:e.id}),"storage.remove":e=>l({type:"storage.remove.result",id:e.id}),"storage.keys":e=>l({type:"storage.keys.result",id:e.id,keys:[]}),"inc.emit":p,"inc.subscribe":e=>l({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":p,"inc.channel.open":e=>l({type:"inc.channel.open.result",id:e.id,channelId:`chan-${String(e.id)}`,peer:"reference-peer"}),"inc.channel.emit":p,"inc.channel.broadcast":p,"inc.channel.list":e=>l({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":p,"theme.get":e=>l({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":p,"keys.registerAction":e=>l({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":p,"media.session.create":e=>l({type:"media.session.create.result",id:e.id,sessionId:`session-${String(e.id)}`,owner:e.owner}),"media.session.update":p,"media.session.destroy":p,"media.state":p,"media.capabilities":p,"notify.send":e=>l({type:"notify.send.result",id:e.id,notificationId:`notif-${String(e.id)}`}),"notify.dismiss":p,"notify.badge":p,"notify.channel.register":p,"notify.permission.request":e=>l({type:"notify.permission.result",id:e.id,granted:!0}),"config.registerSchema":e=>l({type:"config.registerSchema.result",id:e.id,ok:!0}),"config.get":e=>l({type:"config.values",id:e.id,values:{}}),"config.subscribe":()=>l({type:"config.values",values:{}}),"config.unsubscribe":p,"config.openSettings":p,"resource.bytes":e=>l({type:"resource.bytes.result",id:e.id,blob:new Blob([]),mime:"application/octet-stream"}),"resource.cancel":p,"cvm.discover":e=>l({type:"cvm.discover.result",id:e.id,servers:[]}),"cvm.request":e=>l({type:"cvm.request.result",id:e.id,message:{}}),"cvm.close":e=>l({type:"cvm.close.result",id:e.id}),"outbox.query":e=>l({type:"outbox.query.result",id:e.id,events:[],relays:{}}),"outbox.subscribe":e=>l({type:"outbox.eose",subId:e.subId}),"outbox.close":p,"outbox.publish":e=>l({type:"outbox.publish.result",id:e.id,ok:!0}),"outbox.resolveRelays":e=>l({type:"outbox.resolveRelays.result",id:e.id,plan:{}}),"upload.upload":e=>l({type:"upload.upload.result",id:e.id,result:{url:J}}),"upload.status":e=>l({type:"upload.status.result",id:e.id,status:{}}),"intent.invoke":e=>l({type:"intent.invoke.result",id:e.id,result:{}}),"intent.available":e=>l({type:"intent.available.result",id:e.id,availability:{}}),"intent.handlers":e=>l({type:"intent.handlers.result",id:e.id,handlers:[]})};function W(e={}){const t=e.now??(()=>Date.now()),s={domains:e.capabilities?.domains??[...I],protocols:e.capabilities?.protocols??{}},o=e.services??[],i=e.class??null,r=[];function d(c){const n=c&&typeof c=="object"&&typeof c.type=="string"?c.type:void 0;if(n==="shell.ready")return[{type:"shell.init",capabilities:{domains:s.domains,protocols:s.protocols},services:o,class:i}];if(r.push({envelope:c,verdict:U(c),timestamp:t()}),!n)return[];const f=V[n];return f?f(c):[]}return{capabilities:s,services:o,class:i,get records(){return r},handle:d,reset(){r.length=0}}}function Y(e){return{manifestHtml:e.manifestHtml,sandbox:e.sandbox??{allowScripts:!0,allowSameOrigin:!1},installedGlobal:e.boot.installedGlobal,bootError:e.boot.bootError,emitted:e.boot.emitted,forbiddenGlobals:e.forbiddenGlobals??[],degraded:e.boot.degraded,lifecycle:e.lifecycle??null}}function j(e){return new Promise(t=>setTimeout(t,e))}async function x(e,t,s,o){const i=W(e?{capabilities:e}:void 0),r=s.createElement("iframe");r.setAttribute("sandbox",t.allowSameOrigin?"allow-scripts allow-same-origin":"allow-scripts"),r.setAttribute("aria-hidden","true"),r.style.position="absolute",r.style.width="1px",r.style.height="1px",r.style.left="-9999px";let d;const c=new Promise(b=>{d=b}),n=b=>{if(b.source!==r.contentWindow)return;const H=i.handle(b.data),D=b.source;for(const F of H)D?.postMessage(F,"*");const $=b.data;$&&typeof $=="object"&&$.type==="shell.ready"&&d()};o.addEventListener("message",n),(s.body??s.documentElement).appendChild(r),r.setAttribute("src",t.url);let f=!1,v=null;const O=Symbol("timeout");await Promise.race([c.then(()=>"ready"),j(t.readyTimeoutMs).then(()=>O)])===O?v=`napplet did not boot (no shell.ready within ${t.readyTimeoutMs}ms; a conformant napplet must run under sandbox="allow-scripts" without allow-same-origin)`:(f=!0,await j(t.settleMs)),o.removeEventListener("message",n);try{r.remove()}catch{}return{installedGlobal:f,bootError:v,emitted:[...i.records]}}async function X(e){const t=e.document??document,s=e.window??window,o={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},i=await x(void 0,o,t,s);let r=null;{const d=await x({domains:[],protocols:{}},o,t,s);r={bootError:d.bootError,emitted:d.emitted}}return{installedGlobal:i.installedGlobal,bootError:i.bootError,emitted:i.emitted,degraded:r}}var Q=e=>({status:"pass",detail:e}),ee=(e,t)=>({status:"fail",detail:e,diagnostics:t}),te=e=>({status:"skip",detail:e}),u={pass:Q,fail:ee,skip:te};function ie(e,t){return k(e).errors.filter(s=>t.includes(s.code))}function M(e,t){return new RegExp(`name\\s*=\\s*["']${t}["']`,"i").test(e)}function A(e,t,s,o){return{id:e,area:"manifest",severity:"error",title:t,run:i=>{if(o&&!M(i.manifestHtml,o))return u.skip(`No ${o} declared`);const r=ie(i.manifestHtml,s);return r.length?u.fail(r[0].message,r):u.pass()}}}var se=[A("manifest/napplet-type","Declares a valid napplet-type",["missing-napplet-type","invalid-napplet-type"]),A("manifest/declared-naps","napplet-requires lists only real NAP domains",["unknown-required-nap"],"napplet-requires"),A("manifest/config-schema","Config schema is a draft-07 core subset",["invalid-config-schema"],"napplet-config-schema"),{id:"boot/sandbox-allow-scripts",area:"boot",severity:"error",title:"Runs under allow-scripts without allow-same-origin",run:e=>e.sandbox.allowScripts?e.sandbox.allowSameOrigin?u.fail("Napplet relied on allow-same-origin, which a shell must not grant"):u.pass():u.fail("Napplet was not loaded with allow-scripts")},{id:"boot/installs-global",area:"boot",severity:"error",title:"Completes the NAP-SHELL handshake (posts shell.ready)",run:e=>e.installedGlobal?u.pass():u.fail("window.napplet was not present after load (no NAP-SHELL shell.ready)")},{id:"boot/no-boot-error",area:"boot",severity:"error",title:"Boots without an uncaught error (observed via NAP-SHELL shell.ready)",run:e=>e.bootError?u.fail(e.bootError):u.pass()},{id:"boot/no-forbidden-globals",area:"boot",severity:"error",title:"Does not access forbidden globals (e.g. window.nostr)",run:e=>e.forbiddenGlobals.length?u.fail(`Accessed forbidden global(s): ${e.forbiddenGlobals.join(", ")}`,e.forbiddenGlobals):u.pass()},{id:"wire/envelope-well-formed",area:"wire",severity:"error",title:"Every emitted envelope is well-formed",run:e=>{if(e.emitted.length===0)return u.skip("Napplet emitted no envelopes");const t=e.emitted.filter(o=>!o.verdict.ok);if(t.length===0)return u.pass(`${e.emitted.length} envelope(s) valid`);const s=t.map(o=>({envelope:o.envelope,errors:o.verdict.errors}));return u.fail(`${t.length}/${e.emitted.length} emitted envelope(s) invalid`,s)}},{id:"wire/declared-naps-only",area:"wire",severity:"warning",title:"Only emits NAP domains it declared in napplet-requires",run:e=>{if(e.emitted.length===0)return u.skip("Napplet emitted no envelopes");if(!M(e.manifestHtml,"napplet-requires"))return u.skip("No napplet-requires to compare against");const t=new Set(k(e.manifestHtml).requires.map(i=>i.startsWith("nap:")?i.slice(4):i)),o=[...new Set(e.emitted.map(i=>i.verdict.domain).filter(i=>typeof i=="string"))].filter(i=>!t.has(i));return o.length?u.fail(`Emitted undeclared NAP domain(s): ${o.join(", ")}`,o):u.pass()}},{id:"degrade/supports-false",area:"degradation",severity:"error",title:"Does not crash when the NAP-SHELL environment offers no capabilities (supports() false)",run:e=>e.degraded?e.degraded.bootError?u.fail(`Crashed under a no-capability shell: ${e.degraded.bootError}`):u.pass():u.skip("Host did not run the degraded (no-capabilities) pass")},{id:"lifecycle/clean-teardown",area:"lifecycle",severity:"warning",title:"Tears down listeners on unload",run:e=>e.lifecycle?e.lifecycle.listenerLeak?u.fail("Message listeners remained after unload"):u.pass():u.skip("Lifecycle not measured")}];function re(e,t={}){const s=t.now??(()=>Date.now()),o=t.checks??se,i=s(),r=o.map(n=>{const f=n.run(e);return{id:n.id,area:n.area,severity:n.severity,title:n.title,status:f.status,detail:f.detail,diagnostics:f.diagnostics}}),d={total:r.length,passed:r.filter(n=>n.status==="pass").length,failed:r.filter(n=>n.status==="fail").length,skipped:r.filter(n=>n.status==="skip").length,errors:r.filter(n=>n.status==="fail"&&n.severity==="error").length,warnings:r.filter(n=>n.status==="fail"&&n.severity==="warning").length};return{napplet:k(e.manifestHtml).nappletType,startedAt:i,finishedAt:s(),checks:r,summary:d,ok:d.errors===0}}const ne=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,oe="/__conformance__/events",C=new URLSearchParams(location.search),T=C.get("live")==="1";function y(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const g=document.getElementById("app");function ae(){return g.innerHTML=`
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))o(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const d of r.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&o(d)}).observe(document,{childList:!0,subtree:!0});function s(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(i){if(i.ep)return;i.ep=!0;const r=s(i);fetch(i.href,r)}})();var I=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent"],_=["shell"],a={id:"string"},G={"shell.ready":{dir:"out"},"shell.init":{dir:"in"},"relay.subscribe":{dir:"out",fields:{...a,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...a,subId:"string"}},"relay.publish":{dir:"out",fields:{...a,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...a,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...a,filters:"array"}},"relay.event":{dir:"in"},"relay.eose":{dir:"in"},"relay.closed":{dir:"in"},"relay.publish.result":{dir:"in"},"relay.publishEncrypted.result":{dir:"in"},"relay.query.result":{dir:"in"},"identity.getPublicKey":{dir:"out",fields:{...a}},"identity.getRelays":{dir:"out",fields:{...a}},"identity.getProfile":{dir:"out",fields:{...a}},"identity.getFollows":{dir:"out",fields:{...a}},"identity.getList":{dir:"out",fields:{...a,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...a}},"identity.getMutes":{dir:"out",fields:{...a}},"identity.getBlocked":{dir:"out",fields:{...a}},"identity.getBadges":{dir:"out",fields:{...a}},"identity.changed":{dir:"in"},"identity.getPublicKey.result":{dir:"in"},"identity.getRelays.result":{dir:"in"},"identity.getProfile.result":{dir:"in"},"identity.getFollows.result":{dir:"in"},"identity.getList.result":{dir:"in"},"identity.getZaps.result":{dir:"in"},"identity.getMutes.result":{dir:"in"},"identity.getBlocked.result":{dir:"in"},"identity.getBadges.result":{dir:"in"},"storage.get":{dir:"out",fields:{...a,key:"string"}},"storage.set":{dir:"out",fields:{...a,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...a,key:"string"}},"storage.keys":{dir:"out",fields:{...a}},"storage.get.result":{dir:"in"},"storage.set.result":{dir:"in"},"storage.remove.result":{dir:"in"},"storage.keys.result":{dir:"in"},"inc.emit":{dir:"out",fields:{topic:"string"}},"inc.subscribe":{dir:"out",fields:{...a,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...a,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...a}},"inc.channel.close":{dir:"out",fields:{channelId:"string"}},"inc.subscribe.result":{dir:"in"},"inc.event":{dir:"in"},"inc.channel.open.result":{dir:"in"},"inc.channel.event":{dir:"in"},"inc.channel.list.result":{dir:"in"},"inc.channel.closed":{dir:"in"},"theme.get":{dir:"out",fields:{...a}},"theme.get.result":{dir:"in"},"theme.changed":{dir:"in"},"keys.forward":{dir:"out",fields:{key:"string",code:"string",ctrl:"boolean",alt:"boolean",shift:"boolean",meta:"boolean"}},"keys.registerAction":{dir:"out",fields:{...a,action:"object"}},"keys.unregisterAction":{dir:"out",fields:{actionId:"string"}},"keys.registerAction.result":{dir:"in"},"keys.bindings":{dir:"in"},"keys.action":{dir:"in"},"media.session.create":{dir:"out",fields:{...a,owner:"object"}},"media.session.update":{dir:"out",fields:{sessionId:"string",metadata:"object"}},"media.session.destroy":{dir:"out",fields:{sessionId:"string"}},"media.state":{dir:"out",fields:{sessionId:"string",status:"string"}},"media.capabilities":{dir:"out",fields:{sessionId:"string",actions:"array"}},"media.session.create.result":{dir:"in"},"media.command":{dir:"in"},"media.controls":{dir:"in"},"notify.send":{dir:"out",fields:{...a,title:"string"}},"notify.dismiss":{dir:"out",fields:{notificationId:"string"}},"notify.badge":{dir:"out",fields:{count:"number"}},"notify.channel.register":{dir:"out",fields:{channelId:"string",label:"string"}},"notify.permission.request":{dir:"out",fields:{...a}},"notify.send.result":{dir:"in"},"notify.permission.result":{dir:"in"},"notify.action":{dir:"in"},"notify.clicked":{dir:"in"},"notify.dismissed":{dir:"in"},"notify.controls":{dir:"in"},"config.registerSchema":{dir:"out",fields:{...a,schema:"object"}},"config.get":{dir:"out",fields:{...a}},"config.subscribe":{dir:"out"},"config.unsubscribe":{dir:"out"},"config.openSettings":{dir:"out"},"config.registerSchema.result":{dir:"in"},"config.values":{dir:"in"},"config.schemaError":{dir:"in"},"resource.bytes":{dir:"out",fields:{...a,url:"string"}},"resource.cancel":{dir:"out",fields:{...a}},"resource.bytes.result":{dir:"in"},"resource.bytes.error":{dir:"in"},"cvm.discover":{dir:"out",fields:{...a}},"cvm.request":{dir:"out",fields:{...a,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...a,server:"object"}},"cvm.discover.result":{dir:"in"},"cvm.request.result":{dir:"in"},"cvm.close.result":{dir:"in"},"cvm.event":{dir:"in"},"outbox.query":{dir:"out",fields:{...a,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...a,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...a,subId:"string"}},"outbox.publish":{dir:"out",fields:{...a,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...a,target:"present"}},"outbox.query.result":{dir:"in"},"outbox.event":{dir:"in"},"outbox.eose":{dir:"in"},"outbox.closed":{dir:"in"},"outbox.publish.result":{dir:"in"},"outbox.resolveRelays.result":{dir:"in"},"upload.upload":{dir:"out",fields:{...a,request:"object"}},"upload.status":{dir:"out",fields:{...a,uploadId:"string"}},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...a,request:"object"}},"intent.available":{dir:"out",fields:{...a,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...a}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"}};function B(e){if(e===void 0)return"undefined";if(e===null)return"null";if(Array.isArray(e))return"array";const t=typeof e;return t==="string"||t==="number"||t==="boolean"?t:t==="object"?"object":"present"}function K(e,t){return t==="present"?e!=null:B(e)===t}function U(e){const t=[];if(typeof e!="object"||e===null||Array.isArray(e))return{ok:!1,errors:[{code:"not-an-object",message:"Envelope must be a non-null object"}]};const s=e,o=s.type;if(typeof o!="string")return{ok:!1,errors:[{code:"missing-type",message:"Envelope is missing a string `type` field"}]};const i=o.indexOf(".");if(i<=0)return{ok:!1,type:o,errors:[{code:"malformed-type",message:`Envelope type "${o}" is not in domain.action form`}]};const r=o.slice(0,i);if(!(I.includes(r)||_.includes(r)))return{ok:!1,type:o,domain:r,errors:[{code:"unknown-domain",message:`"${r}" is not a known NAP domain`}]};const p=G[o];if(!p)return{ok:!1,type:o,domain:r,errors:[{code:"unknown-type",message:`"${o}" is not a known ${r} message type`}]};if(p.dir==="in")return{ok:!1,type:o,domain:r,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${o}" is a shell→napplet message; a napplet must not emit it`}]};for(const[n,m]of Object.entries(p.fields??{})){if(!(n in s)||s[n]===void 0){t.push({code:"missing-field",message:`Required field "${n}" is missing`,field:n});continue}K(s[n],m)||t.push({code:"wrong-type",message:`Field "${n}" should be ${m} but is ${B(s[n])}`,field:n})}return{ok:t.length===0,type:o,domain:r,direction:"out",errors:t}}var R=/^[a-z0-9][a-z0-9._:-]*$/;function Z(e){return e.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g,(t,s)=>{if(s[0]==="#"){const i=s[1]==="x"||s[1]==="X"?parseInt(s.slice(2),16):parseInt(s.slice(1),10);return Number.isFinite(i)?String.fromCodePoint(i):t}return{amp:"&",lt:"<",gt:">",quot:'"',apos:"'"}[s]??t})}function S(e,t){const s=/<meta\b[^>]*>/gi;let o;for(;(o=s.exec(e))!==null;){const i=o[0],r=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);if((r?.[1]??r?.[2]??"").trim()!==t)continue;const p=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);return Z((p?.[1]??p?.[2]??"").trim())}}function L(e){if(Array.isArray(e))return e.some(L);if(e&&typeof e=="object"){const t=e;return"pattern"in t?!0:Object.values(t).some(L)}return!1}function k(e,t={}){const s=[],o=[],i=S(e,"napplet-type");i?R.test(i)||s.push({code:"invalid-napplet-type",message:`napplet-type "${i}" must match ${R}`}):s.push({code:"missing-napplet-type",message:'No <meta name="napplet-type"> declared'});const r=S(e,"napplet-requires"),d=r?r.split(",").map(n=>n.trim()).filter(Boolean):[];for(const n of d){const m=n.startsWith("nap:")?n.slice(4):n;I.includes(m)||s.push({code:"unknown-required-nap",message:`napplet-requires lists "${n}" which is not a known NAP domain`})}const p=S(e,"napplet-config-schema");if(p){let n;try{n=JSON.parse(p)}catch{n=void 0,s.push({code:"invalid-config-schema",message:"napplet-config-schema is not valid JSON"})}n!==void 0&&(typeof n!="object"||n===null||Array.isArray(n)?s.push({code:"invalid-config-schema",message:"napplet-config-schema must be a JSON Schema object"}):L(n)&&s.push({code:"invalid-config-schema",message:"napplet-config-schema uses the `pattern` keyword, which the draft-07 core subset excludes (CVE-2025-69873)"}))}return{ok:s.length===0,nappletType:i,requires:d,errors:s,warnings:o}}var z="f".repeat(64),J="https://reference.invalid/blob",l=e=>[e],c=()=>[],V={"relay.subscribe":e=>l({type:"relay.eose",subId:e.subId}),"relay.close":c,"relay.publish":e=>l({type:"relay.publish.result",id:e.id,ok:!0,event:e.event}),"relay.publishEncrypted":e=>l({type:"relay.publishEncrypted.result",id:e.id,ok:!0,event:e.event}),"relay.query":e=>l({type:"relay.query.result",id:e.id,events:[]}),"identity.getPublicKey":e=>l({type:"identity.getPublicKey.result",id:e.id,pubkey:z}),"identity.getRelays":e=>l({type:"identity.getRelays.result",id:e.id,relays:{}}),"identity.getProfile":e=>l({type:"identity.getProfile.result",id:e.id,profile:null}),"identity.getFollows":e=>l({type:"identity.getFollows.result",id:e.id,pubkeys:[]}),"identity.getList":e=>l({type:"identity.getList.result",id:e.id,entries:[]}),"identity.getZaps":e=>l({type:"identity.getZaps.result",id:e.id,zaps:[]}),"identity.getMutes":e=>l({type:"identity.getMutes.result",id:e.id,pubkeys:[]}),"identity.getBlocked":e=>l({type:"identity.getBlocked.result",id:e.id,pubkeys:[]}),"identity.getBadges":e=>l({type:"identity.getBadges.result",id:e.id,badges:[]}),"storage.get":e=>l({type:"storage.get.result",id:e.id,value:null}),"storage.set":e=>l({type:"storage.set.result",id:e.id}),"storage.remove":e=>l({type:"storage.remove.result",id:e.id}),"storage.keys":e=>l({type:"storage.keys.result",id:e.id,keys:[]}),"inc.emit":c,"inc.subscribe":e=>l({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":c,"inc.channel.open":e=>l({type:"inc.channel.open.result",id:e.id,channelId:`chan-${String(e.id)}`,peer:"reference-peer"}),"inc.channel.emit":c,"inc.channel.broadcast":c,"inc.channel.list":e=>l({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":c,"theme.get":e=>l({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":c,"keys.registerAction":e=>l({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":c,"media.session.create":e=>l({type:"media.session.create.result",id:e.id,sessionId:`session-${String(e.id)}`,owner:e.owner}),"media.session.update":c,"media.session.destroy":c,"media.state":c,"media.capabilities":c,"notify.send":e=>l({type:"notify.send.result",id:e.id,notificationId:`notif-${String(e.id)}`}),"notify.dismiss":c,"notify.badge":c,"notify.channel.register":c,"notify.permission.request":e=>l({type:"notify.permission.result",id:e.id,granted:!0}),"config.registerSchema":e=>l({type:"config.registerSchema.result",id:e.id,ok:!0}),"config.get":e=>l({type:"config.values",id:e.id,values:{}}),"config.subscribe":()=>l({type:"config.values",values:{}}),"config.unsubscribe":c,"config.openSettings":c,"resource.bytes":e=>l({type:"resource.bytes.result",id:e.id,blob:new Blob([]),mime:"application/octet-stream"}),"resource.cancel":c,"cvm.discover":e=>l({type:"cvm.discover.result",id:e.id,servers:[]}),"cvm.request":e=>l({type:"cvm.request.result",id:e.id,message:{}}),"cvm.close":e=>l({type:"cvm.close.result",id:e.id}),"outbox.query":e=>l({type:"outbox.query.result",id:e.id,events:[],relays:{}}),"outbox.subscribe":e=>l({type:"outbox.eose",subId:e.subId}),"outbox.close":c,"outbox.publish":e=>l({type:"outbox.publish.result",id:e.id,ok:!0}),"outbox.resolveRelays":e=>l({type:"outbox.resolveRelays.result",id:e.id,plan:{}}),"upload.upload":e=>l({type:"upload.upload.result",id:e.id,result:{url:J}}),"upload.status":e=>l({type:"upload.status.result",id:e.id,status:{}}),"intent.invoke":e=>l({type:"intent.invoke.result",id:e.id,result:{}}),"intent.available":e=>l({type:"intent.available.result",id:e.id,availability:{}}),"intent.handlers":e=>l({type:"intent.handlers.result",id:e.id,handlers:[]})};function W(e={}){const t=e.now??(()=>Date.now()),s={domains:e.capabilities?.domains??[...I],protocols:e.capabilities?.protocols??{}},o=e.services??[],i=[];function r(d){const p=d&&typeof d=="object"&&typeof d.type=="string"?d.type:void 0;if(p==="shell.ready")return[{type:"shell.init",capabilities:{domains:s.domains,protocols:s.protocols},services:o}];if(i.push({envelope:d,verdict:U(d),timestamp:t()}),!p)return[];const n=V[p];return n?n(d):[]}return{capabilities:s,services:o,get records(){return i},handle:r,reset(){i.length=0}}}function Y(e){return{manifestHtml:e.manifestHtml,sandbox:e.sandbox??{allowScripts:!0,allowSameOrigin:!1},installedGlobal:e.boot.installedGlobal,bootError:e.boot.bootError,emitted:e.boot.emitted,forbiddenGlobals:e.forbiddenGlobals??[],degraded:e.boot.degraded,lifecycle:e.lifecycle??null}}function j(e){return new Promise(t=>setTimeout(t,e))}async function x(e,t,s,o){const i=W(e?{capabilities:e}:void 0),r=s.createElement("iframe");r.setAttribute("sandbox",t.allowSameOrigin?"allow-scripts allow-same-origin":"allow-scripts"),r.setAttribute("aria-hidden","true"),r.style.position="absolute",r.style.width="1px",r.style.height="1px",r.style.left="-9999px";let d;const p=new Promise(b=>{d=b}),n=b=>{if(b.source!==r.contentWindow)return;const H=i.handle(b.data),D=b.source;for(const F of H)D?.postMessage(F,"*");const $=b.data;$&&typeof $=="object"&&$.type==="shell.ready"&&d()};o.addEventListener("message",n),(s.body??s.documentElement).appendChild(r),r.setAttribute("src",t.url);let m=!1,v=null;const O=Symbol("timeout");await Promise.race([p.then(()=>"ready"),j(t.readyTimeoutMs).then(()=>O)])===O?v=`napplet did not boot (no shell.ready within ${t.readyTimeoutMs}ms; a conformant napplet must run under sandbox="allow-scripts" without allow-same-origin)`:(m=!0,await j(t.settleMs)),o.removeEventListener("message",n);try{r.remove()}catch{}return{installedGlobal:m,bootError:v,emitted:[...i.records]}}async function X(e){const t=e.document??document,s=e.window??window,o={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},i=await x(void 0,o,t,s);let r=null;{const d=await x({domains:[],protocols:{}},o,t,s);r={bootError:d.bootError,emitted:d.emitted}}return{installedGlobal:i.installedGlobal,bootError:i.bootError,emitted:i.emitted,degraded:r}}var Q=e=>({status:"pass",detail:e}),ee=(e,t)=>({status:"fail",detail:e,diagnostics:t}),te=e=>({status:"skip",detail:e}),u={pass:Q,fail:ee,skip:te};function ie(e,t){return k(e).errors.filter(s=>t.includes(s.code))}function M(e,t){return new RegExp(`name\\s*=\\s*["']${t}["']`,"i").test(e)}function A(e,t,s,o){return{id:e,area:"manifest",severity:"error",title:t,run:i=>{if(o&&!M(i.manifestHtml,o))return u.skip(`No ${o} declared`);const r=ie(i.manifestHtml,s);return r.length?u.fail(r[0].message,r):u.pass()}}}var se=[A("manifest/napplet-type","Declares a valid napplet-type",["missing-napplet-type","invalid-napplet-type"]),A("manifest/declared-naps","napplet-requires lists only real NAP domains",["unknown-required-nap"],"napplet-requires"),A("manifest/config-schema","Config schema is a draft-07 core subset",["invalid-config-schema"],"napplet-config-schema"),{id:"boot/sandbox-allow-scripts",area:"boot",severity:"error",title:"Runs under allow-scripts without allow-same-origin",run:e=>e.sandbox.allowScripts?e.sandbox.allowSameOrigin?u.fail("Napplet relied on allow-same-origin, which a shell must not grant"):u.pass():u.fail("Napplet was not loaded with allow-scripts")},{id:"boot/installs-global",area:"boot",severity:"error",title:"Completes the NAP-SHELL handshake (posts shell.ready)",run:e=>e.installedGlobal?u.pass():u.fail("window.napplet was not present after load (no NAP-SHELL shell.ready)")},{id:"boot/no-boot-error",area:"boot",severity:"error",title:"Boots without an uncaught error (observed via NAP-SHELL shell.ready)",run:e=>e.bootError?u.fail(e.bootError):u.pass()},{id:"boot/no-forbidden-globals",area:"boot",severity:"error",title:"Does not access forbidden globals (e.g. window.nostr)",run:e=>e.forbiddenGlobals.length?u.fail(`Accessed forbidden global(s): ${e.forbiddenGlobals.join(", ")}`,e.forbiddenGlobals):u.pass()},{id:"wire/envelope-well-formed",area:"wire",severity:"error",title:"Every emitted envelope is well-formed",run:e=>{if(e.emitted.length===0)return u.skip("Napplet emitted no envelopes");const t=e.emitted.filter(o=>!o.verdict.ok);if(t.length===0)return u.pass(`${e.emitted.length} envelope(s) valid`);const s=t.map(o=>({envelope:o.envelope,errors:o.verdict.errors}));return u.fail(`${t.length}/${e.emitted.length} emitted envelope(s) invalid`,s)}},{id:"wire/declared-naps-only",area:"wire",severity:"warning",title:"Only emits NAP domains it declared in napplet-requires",run:e=>{if(e.emitted.length===0)return u.skip("Napplet emitted no envelopes");if(!M(e.manifestHtml,"napplet-requires"))return u.skip("No napplet-requires to compare against");const t=new Set(k(e.manifestHtml).requires.map(i=>i.startsWith("nap:")?i.slice(4):i)),o=[...new Set(e.emitted.map(i=>i.verdict.domain).filter(i=>typeof i=="string"))].filter(i=>!t.has(i));return o.length?u.fail(`Emitted undeclared NAP domain(s): ${o.join(", ")}`,o):u.pass()}},{id:"degrade/supports-false",area:"degradation",severity:"error",title:"Does not crash when the NAP-SHELL environment offers no capabilities (supports() false)",run:e=>e.degraded?e.degraded.bootError?u.fail(`Crashed under a no-capability shell: ${e.degraded.bootError}`):u.pass():u.skip("Host did not run the degraded (no-capabilities) pass")},{id:"lifecycle/clean-teardown",area:"lifecycle",severity:"warning",title:"Tears down listeners on unload",run:e=>e.lifecycle?e.lifecycle.listenerLeak?u.fail("Message listeners remained after unload"):u.pass():u.skip("Lifecycle not measured")}];function re(e,t={}){const s=t.now??(()=>Date.now()),o=t.checks??se,i=s(),r=o.map(n=>{const m=n.run(e);return{id:n.id,area:n.area,severity:n.severity,title:n.title,status:m.status,detail:m.detail,diagnostics:m.diagnostics}}),d={total:r.length,passed:r.filter(n=>n.status==="pass").length,failed:r.filter(n=>n.status==="fail").length,skipped:r.filter(n=>n.status==="skip").length,errors:r.filter(n=>n.status==="fail"&&n.severity==="error").length,warnings:r.filter(n=>n.status==="fail"&&n.severity==="warning").length};return{napplet:k(e.manifestHtml).nappletType,startedAt:i,finishedAt:s(),checks:r,summary:d,ok:d.errors===0}}const ne=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,oe="/__conformance__/events",C=new URLSearchParams(location.search),T=C.get("live")==="1";function y(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const g=document.getElementById("app");function ae(){return g.innerHTML=`
2
2
  <header class="masthead">
3
3
  <h1>napplet conformance ${T?'<span class="live-badge" id="live-badge">● live</span>':""}</h1>
4
4
  <p>Load a napplet by URL and run NAP protocol conformance live. Same engine as <code>napplet-conformance</code> CI.</p>
@@ -10,7 +10,7 @@
10
10
  </form>
11
11
  <p id="status" class="status" role="status"></p>
12
12
  <section id="output" class="output"></section>
13
- `,{urlInput:g.querySelector("#url"),runButton:g.querySelector("#run"),rerunButton:g.querySelector("#rerun"),liveBadge:g.querySelector("#live-badge")??document.createElement("span"),status:g.querySelector("#status"),output:g.querySelector("#output")}}const m=ae();let h="",P=0;function w(e,t="info"){m.status.textContent=e,m.status.dataset.kind=t}function N(e){return String(e).padStart(2,"0")}function le(e){const t=new Date(e);return`${N(t.getHours())}:${N(t.getMinutes())}:${N(t.getSeconds())}`}async function de(e){try{const s=await(await fetch(e,{mode:"cors"})).text();return{html:s,forbidden:ne.test(s)?["window.nostr"]:[],fetched:!0}}catch{return{html:"",forbidden:[],fetched:!1}}}function ue(e){const t=e.ok?"conformant":"non-conformant",o=[...new Set(e.checks.map(d=>d.area))].map(d=>{const c=e.checks.filter(n=>n.area===d).map(n=>{const f=n.severity==="warning"&&n.status==="fail"?' <span class="tag warn">warning</span>':"",v=n.detail?`<span class="detail">${y(n.detail)}</span>`:"";return`<li class="check ${n.status}"><span class="badge ${n.status}">${n.status}</span><span class="id">${y(n.id)}</span>${f}${v}</li>`}).join("");return`<div class="area"><h3>${y(d)}</h3><ul>${c}</ul></div>`}).join(""),i=e.summary,r=P>1?`<span class="run-stamp">run #${P} · ${le(e.finishedAt)}</span>`:"";return`
13
+ `,{urlInput:g.querySelector("#url"),runButton:g.querySelector("#run"),rerunButton:g.querySelector("#rerun"),liveBadge:g.querySelector("#live-badge")??document.createElement("span"),status:g.querySelector("#status"),output:g.querySelector("#output")}}const f=ae();let h="",P=0;function w(e,t="info"){f.status.textContent=e,f.status.dataset.kind=t}function N(e){return String(e).padStart(2,"0")}function le(e){const t=new Date(e);return`${N(t.getHours())}:${N(t.getMinutes())}:${N(t.getSeconds())}`}async function de(e){try{const s=await(await fetch(e,{mode:"cors"})).text();return{html:s,forbidden:ne.test(s)?["window.nostr"]:[],fetched:!0}}catch{return{html:"",forbidden:[],fetched:!1}}}function ue(e){const t=e.ok?"conformant":"non-conformant",o=[...new Set(e.checks.map(d=>d.area))].map(d=>{const p=e.checks.filter(n=>n.area===d).map(n=>{const m=n.severity==="warning"&&n.status==="fail"?' <span class="tag warn">warning</span>':"",v=n.detail?`<span class="detail">${y(n.detail)}</span>`:"";return`<li class="check ${n.status}"><span class="badge ${n.status}">${n.status}</span><span class="id">${y(n.id)}</span>${m}${v}</li>`}).join("");return`<div class="area"><h3>${y(d)}</h3><ul>${p}</ul></div>`}).join(""),i=e.summary,r=P>1?`<span class="run-stamp">run #${P} · ${le(e.finishedAt)}</span>`:"";return`
14
14
  <div class="verdict ${t}">
15
15
  <span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
16
16
  <span class="counts">${i.passed} passed · ${i.failed} failed · ${i.skipped} skipped${i.warnings?` · ${i.warnings} warning(s)`:""}</span>
@@ -23,10 +23,10 @@
23
23
  ${o("requires",s.requires.join(", "))}
24
24
  </table>
25
25
  ${s.errors.length?`<div class="errs">${s.errors.map(i=>y(`${i.code}: ${i.message}`)).join("<br>")}</div>`:""}
26
- `}function fe(e,t,s,o){m.output.innerHTML=`
26
+ `}function fe(e,t,s,o){f.output.innerHTML=`
27
27
  ${ue(e)}
28
28
  <div class="panels">
29
29
  <section class="panel"><h2>Emitted envelopes (${t.length})</h2>${ce(t)}</section>
30
30
  <section class="panel"><h2>Manifest</h2>${pe(s,o)}</section>
31
31
  </div>
32
- `}async function E(e){h=e,m.runButton.disabled=!0,m.rerunButton.disabled=!0;try{w(`Fetching manifest for ${e} …`);const{html:t,forbidden:s,fetched:o}=await de(e);w("Booting napplet in a sandboxed iframe …");const i=await X({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),r=Y({manifestHtml:t,boot:i,forbiddenGlobals:s}),d=re(r);P+=1,w(d.ok?"Done — conformant.":"Done — non-conformant.",d.ok?"info":"error"),fe(d,i.emitted,t,o),m.rerunButton.hidden=!1}catch(t){w(`Error: ${t instanceof Error?t.message:String(t)}`,"error")}finally{m.runButton.disabled=!1,m.rerunButton.disabled=!1}}function me(){if(!T||typeof EventSource>"u")return;let e=!1;const t=new EventSource(oe);t.addEventListener("rerun",()=>{!h||e||(e=!0,setTimeout(()=>{e=!1,E(h)},50))}),t.addEventListener("open",()=>{m.liveBadge&&(m.liveBadge.dataset.state="connected")}),t.addEventListener("error",()=>{m.liveBadge&&(m.liveBadge.dataset.state="disconnected")})}m.runButton.parentElement.addEventListener("submit",e=>{e.preventDefault();const t=m.urlInput.value.trim();if(t){const s=new URL(location.href);s.searchParams.set("url",t),history.replaceState(null,"",s),E(t)}});m.rerunButton.addEventListener("click",()=>{h&&E(h)});me();const q=C.get("url");q&&(m.urlInput.value=q,E(q));
32
+ `}async function E(e){h=e,f.runButton.disabled=!0,f.rerunButton.disabled=!0;try{w(`Fetching manifest for ${e} …`);const{html:t,forbidden:s,fetched:o}=await de(e);w("Booting napplet in a sandboxed iframe …");const i=await X({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),r=Y({manifestHtml:t,boot:i,forbiddenGlobals:s}),d=re(r);P+=1,w(d.ok?"Done — conformant.":"Done — non-conformant.",d.ok?"info":"error"),fe(d,i.emitted,t,o),f.rerunButton.hidden=!1}catch(t){w(`Error: ${t instanceof Error?t.message:String(t)}`,"error")}finally{f.runButton.disabled=!1,f.rerunButton.disabled=!1}}function me(){if(!T||typeof EventSource>"u")return;let e=!1;const t=new EventSource(oe);t.addEventListener("rerun",()=>{!h||e||(e=!0,setTimeout(()=>{e=!1,E(h)},50))}),t.addEventListener("open",()=>{f.liveBadge&&(f.liveBadge.dataset.state="connected")}),t.addEventListener("error",()=>{f.liveBadge&&(f.liveBadge.dataset.state="disconnected")})}f.runButton.parentElement.addEventListener("submit",e=>{e.preventDefault();const t=f.urlInput.value.trim();if(t){const s=new URL(location.href);s.searchParams.set("url",t),history.replaceState(null,"",s),E(t)}});f.rerunButton.addEventListener("click",()=>{h&&E(h)});me();const q=C.get("url");q&&(f.urlInput.value=q,E(q));
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>napplet conformance runtime</title>
7
7
  <meta name="description" content="Load a napplet and run NAP protocol conformance checks live in your browser." />
8
- <script type="module" crossorigin src="/assets/index-Cn9jwNt-.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DfDcF0hE.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-D4WUxbBl.css">
10
10
  </head>
11
11
  <body>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@napplet/conformance-cli",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "Headless CLI that runs @napplet/conformance against a napplet in real Chromium via Playwright — the `test:conformance` runner",
5
5
  "type": "module",
6
6
  "bin": {
@@ -15,13 +15,13 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "playwright": "^1.59.1",
18
- "@napplet/conformance": "^0.2.1"
18
+ "@napplet/conformance": "^0.3.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^22.15.0",
22
22
  "tsup": "^8.5.0",
23
23
  "typescript": "^5.9.3",
24
- "@napplet/conformance-web": "0.0.2"
24
+ "@napplet/conformance-web": "0.0.3"
25
25
  },
26
26
  "license": "MIT",
27
27
  "repository": {