@napplet/conformance-cli 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  var NappletConformanceHostBundle = (() => {
3
3
  // ../core/dist/index.js
4
- var NAP_DOMAINS = ["relay", "identity", "storage", "inc", "theme", "keys", "media", "notify", "config", "resource", "cvm", "outbox", "upload", "intent", "ble", "webrtc", "link", "lists", "serial", "common"];
4
+ var NAP_DOMAINS = ["relay", "identity", "storage", "inc", "theme", "keys", "media", "notify", "config", "resource", "cvm", "outbox", "upload", "intent", "ble", "webrtc", "link", "lists", "serial", "common", "dm"];
5
5
  function createDispatch() {
6
6
  const handlers = /* @__PURE__ */ new Map();
7
7
  function registerNap2(domain, handler) {
@@ -238,7 +238,21 @@ var NappletConformanceHostBundle = (() => {
238
238
  "serial.open.result": { dir: "in" },
239
239
  "serial.write.result": { dir: "in" },
240
240
  "serial.close.result": { dir: "in" },
241
- "serial.event": { dir: "in" }
241
+ "serial.event": { dir: "in" },
242
+ // ── dm ───────────────────────────────────────────────────────────────────
243
+ "dm.status": { dir: "out", fields: { ...ID } },
244
+ "dm.conversations": { dir: "out", fields: { ...ID } },
245
+ "dm.messages": { dir: "out", fields: { ...ID, conversationId: "string" } },
246
+ "dm.send": { dir: "out", fields: { ...ID, recipients: "array", content: "string" } },
247
+ "dm.subscribe": { dir: "out", fields: { ...ID } },
248
+ "dm.unsubscribe": { dir: "out", fields: { ...ID, subscriptionId: "string" } },
249
+ "dm.status.result": { dir: "in" },
250
+ "dm.conversations.result": { dir: "in" },
251
+ "dm.messages.result": { dir: "in" },
252
+ "dm.send.result": { dir: "in" },
253
+ "dm.subscribe.result": { dir: "in" },
254
+ "dm.unsubscribe.result": { dir: "in" },
255
+ "dm.message": { dir: "in" }
242
256
  };
243
257
  function kindOf(value) {
244
258
  if (value === void 0) return "undefined";
@@ -0,0 +1,32 @@
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))l(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const a of o.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&l(a)}).observe(document,{childList:!0,subtree:!0});function r(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function l(s){if(s.ep)return;s.ep=!0;const o=r(s);fetch(s.href,o)}})();var L=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent","ble","webrtc","link","lists","serial","common","dm"],G=["shell"],i={id:"string"},K={"shell.ready":{dir:"out"},"shell.init":{dir:"in"},"relay.subscribe":{dir:"out",fields:{...i,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...i,subId:"string"}},"relay.publish":{dir:"out",fields:{...i,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...i,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...i,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:{...i}},"identity.getRelays":{dir:"out",fields:{...i}},"identity.getProfile":{dir:"out",fields:{...i}},"identity.getFollows":{dir:"out",fields:{...i}},"identity.getList":{dir:"out",fields:{...i,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...i}},"identity.getMutes":{dir:"out",fields:{...i}},"identity.getBlocked":{dir:"out",fields:{...i}},"identity.getBadges":{dir:"out",fields:{...i}},"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:{...i,key:"string"}},"storage.set":{dir:"out",fields:{...i,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...i,key:"string"}},"storage.keys":{dir:"out",fields:{...i}},"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:{...i,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...i,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...i}},"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:{...i}},"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:{...i,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:{...i,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:{...i,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:{...i}},"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:{...i,schema:"object"}},"config.get":{dir:"out",fields:{...i}},"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:{...i,url:"string"}},"resource.bytesMany":{dir:"out",fields:{...i,urls:"array"}},"resource.cancel":{dir:"out",fields:{...i}},"resource.bytes.result":{dir:"in"},"resource.bytes.error":{dir:"in"},"resource.bytesMany.result":{dir:"in"},"resource.bytesMany.error":{dir:"in"},"cvm.discover":{dir:"out",fields:{...i}},"cvm.request":{dir:"out",fields:{...i,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...i,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:{...i,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...i,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...i,subId:"string"}},"outbox.publish":{dir:"out",fields:{...i,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...i,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:{...i,request:"object"}},"upload.status":{dir:"out",fields:{...i,uploadId:"string"}},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...i,request:"object"}},"intent.available":{dir:"out",fields:{...i,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...i}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"},"ble.open":{dir:"out",fields:{...i,request:"object"}},"ble.services":{dir:"out",fields:{...i,sessionId:"string"}},"ble.read":{dir:"out",fields:{...i,sessionId:"string",target:"object"}},"ble.write":{dir:"out",fields:{...i,sessionId:"string",target:"object",data:"array"}},"ble.subscribe":{dir:"out",fields:{...i,sessionId:"string",target:"object"}},"ble.unsubscribe":{dir:"out",fields:{...i,sessionId:"string",target:"object"}},"ble.close":{dir:"out",fields:{...i,sessionId:"string"}},"ble.open.result":{dir:"in"},"ble.services.result":{dir:"in"},"ble.read.result":{dir:"in"},"ble.write.result":{dir:"in"},"ble.subscribe.result":{dir:"in"},"ble.unsubscribe.result":{dir:"in"},"ble.close.result":{dir:"in"},"ble.event":{dir:"in"},"common.encodeNip19":{dir:"out",fields:{...i,input:"object"}},"common.decodeNip19":{dir:"out",fields:{...i,value:"string"}},"common.getProfile":{dir:"out",fields:{...i,target:"string"}},"common.follows":{dir:"out",fields:{...i}},"common.follow":{dir:"out",fields:{...i,pubkeys:"array"}},"common.unfollow":{dir:"out",fields:{...i,pubkeys:"array"}},"common.react":{dir:"out",fields:{...i,targetEventId:"string",reaction:"string"}},"common.report":{dir:"out",fields:{...i,target:"object",reason:"string",text:"string"}},"common.encodeNip19.result":{dir:"in"},"common.decodeNip19.result":{dir:"in"},"common.getProfile.result":{dir:"in"},"common.follows.result":{dir:"in"},"common.follow.result":{dir:"in"},"common.unfollow.result":{dir:"in"},"common.react.result":{dir:"in"},"common.report.result":{dir:"in"},"webrtc.open":{dir:"out",fields:{...i,request:"object"}},"webrtc.send":{dir:"out",fields:{...i,sessionId:"string",payload:"present"}},"webrtc.close":{dir:"out",fields:{...i,sessionId:"string"}},"webrtc.open.result":{dir:"in"},"webrtc.send.result":{dir:"in"},"webrtc.close.result":{dir:"in"},"webrtc.event":{dir:"in"},"link.open":{dir:"out",fields:{...i,url:"string"}},"link.open.result":{dir:"in"},"lists.supported":{dir:"out",fields:{...i}},"lists.add":{dir:"out",fields:{...i,list:"object",items:"array"}},"lists.remove":{dir:"out",fields:{...i,list:"object",items:"array"}},"lists.supported.result":{dir:"in"},"lists.add.result":{dir:"in"},"lists.remove.result":{dir:"in"},"serial.open":{dir:"out",fields:{...i,request:"object"}},"serial.write":{dir:"out",fields:{...i,sessionId:"string",data:"array"}},"serial.close":{dir:"out",fields:{...i,sessionId:"string"}},"serial.open.result":{dir:"in"},"serial.write.result":{dir:"in"},"serial.close.result":{dir:"in"},"serial.event":{dir:"in"},"dm.status":{dir:"out",fields:{...i}},"dm.conversations":{dir:"out",fields:{...i}},"dm.messages":{dir:"out",fields:{...i,conversationId:"string"}},"dm.send":{dir:"out",fields:{...i,recipients:"array",content:"string"}},"dm.subscribe":{dir:"out",fields:{...i}},"dm.unsubscribe":{dir:"out",fields:{...i,subscriptionId:"string"}},"dm.status.result":{dir:"in"},"dm.conversations.result":{dir:"in"},"dm.messages.result":{dir:"in"},"dm.send.result":{dir:"in"},"dm.subscribe.result":{dir:"in"},"dm.unsubscribe.result":{dir:"in"},"dm.message":{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 U(e,t){return t==="present"?e!=null:B(e)===t}function Z(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 r=e,l=r.type;if(typeof l!="string")return{ok:!1,errors:[{code:"missing-type",message:"Envelope is missing a string `type` field"}]};const s=l.indexOf(".");if(s<=0)return{ok:!1,type:l,errors:[{code:"malformed-type",message:`Envelope type "${l}" is not in domain.action form`}]};const o=l.slice(0,s);if(!(L.includes(o)||G.includes(o)))return{ok:!1,type:l,domain:o,errors:[{code:"unknown-domain",message:`"${o}" is not a known NAP domain`}]};const p=K[l];if(!p)return{ok:!1,type:l,domain:o,errors:[{code:"unknown-type",message:`"${l}" is not a known ${o} message type`}]};if(p.dir==="in")return{ok:!1,type:l,domain:o,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${l}" is a shell→napplet message; a napplet must not emit it`}]};for(const[d,m]of Object.entries(p.fields??{})){if(!(d in r)||r[d]===void 0){t.push({code:"missing-field",message:`Required field "${d}" is missing`,field:d});continue}U(r[d],m)||t.push({code:"wrong-type",message:`Field "${d}" should be ${m} but is ${B(r[d])}`,field:d})}return{ok:t.length===0,type:l,domain:o,direction:"out",errors:t}}var M=/^[a-z0-9][a-z0-9._:-]*$/;function z(e){return e.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g,(t,r)=>{if(r[0]==="#"){const s=r[1]==="x"||r[1]==="X"?parseInt(r.slice(2),16):parseInt(r.slice(1),10);return Number.isFinite(s)?String.fromCodePoint(s):t}return{amp:"&",lt:"<",gt:">",quot:'"',apos:"'"}[r]??t})}function N(e,t){const r=/<meta\b[^>]*>/gi;let l;for(;(l=r.exec(e))!==null;){const s=l[0],o=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(s);if((o?.[1]??o?.[2]??"").trim()!==t)continue;const p=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(s);return z((p?.[1]??p?.[2]??"").trim())}}function q(e){if(Array.isArray(e))return e.some(q);if(e&&typeof e=="object"){const t=e;return"pattern"in t?!0:Object.values(t).some(q)}return!1}function E(e,t={}){const r=[],l=[],s=N(e,"napplet-type");s?M.test(s)||r.push({code:"invalid-napplet-type",message:`napplet-type "${s}" must match ${M}`}):r.push({code:"missing-napplet-type",message:'No <meta name="napplet-type"> declared'});const o=N(e,"napplet-requires"),a=o?o.split(",").map(d=>d.trim()).filter(Boolean):[];for(const d of a){const m=d.startsWith("nap:")?d.slice(4):d;L.includes(m)||r.push({code:"unknown-required-nap",message:`napplet-requires lists "${d}" which is not a known NAP domain`})}const p=N(e,"napplet-config-schema");if(p){let d;try{d=JSON.parse(p)}catch{d=void 0,r.push({code:"invalid-config-schema",message:"napplet-config-schema is not valid JSON"})}d!==void 0&&(typeof d!="object"||d===null||Array.isArray(d)?r.push({code:"invalid-config-schema",message:"napplet-config-schema must be a JSON Schema object"}):q(d)&&r.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:r.length===0,nappletType:s,requires:a,errors:r,warnings:l}}var w="f".repeat(64),J="https://reference.invalid/blob",n=e=>[e],c=()=>[],V={"relay.subscribe":e=>n({type:"relay.eose",subId:e.subId}),"relay.close":c,"relay.publish":e=>n({type:"relay.publish.result",id:e.id,ok:!0,event:e.event}),"relay.publishEncrypted":e=>n({type:"relay.publishEncrypted.result",id:e.id,ok:!0,event:e.event}),"relay.query":e=>n({type:"relay.query.result",id:e.id,events:[]}),"identity.getPublicKey":e=>n({type:"identity.getPublicKey.result",id:e.id,pubkey:w}),"identity.getRelays":e=>n({type:"identity.getRelays.result",id:e.id,relays:{}}),"identity.getProfile":e=>n({type:"identity.getProfile.result",id:e.id,profile:null}),"identity.getFollows":e=>n({type:"identity.getFollows.result",id:e.id,pubkeys:[]}),"identity.getList":e=>n({type:"identity.getList.result",id:e.id,entries:[]}),"identity.getZaps":e=>n({type:"identity.getZaps.result",id:e.id,zaps:[]}),"identity.getMutes":e=>n({type:"identity.getMutes.result",id:e.id,pubkeys:[]}),"identity.getBlocked":e=>n({type:"identity.getBlocked.result",id:e.id,pubkeys:[]}),"identity.getBadges":e=>n({type:"identity.getBadges.result",id:e.id,badges:[]}),"storage.get":e=>n({type:"storage.get.result",id:e.id,value:null}),"storage.set":e=>n({type:"storage.set.result",id:e.id}),"storage.remove":e=>n({type:"storage.remove.result",id:e.id}),"storage.keys":e=>n({type:"storage.keys.result",id:e.id,keys:[]}),"inc.emit":c,"inc.subscribe":e=>n({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":c,"inc.channel.open":e=>n({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=>n({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":c,"theme.get":e=>n({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":c,"keys.registerAction":e=>n({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":c,"media.session.create":e=>n({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=>n({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=>n({type:"notify.permission.result",id:e.id,granted:!0}),"config.registerSchema":e=>n({type:"config.registerSchema.result",id:e.id,ok:!0}),"config.get":e=>n({type:"config.values",id:e.id,values:{}}),"config.subscribe":()=>n({type:"config.values",values:{}}),"config.unsubscribe":c,"config.openSettings":c,"resource.bytes":e=>n({type:"resource.bytes.result",id:e.id,blob:new Blob([]),mime:"application/octet-stream"}),"resource.bytesMany":e=>n({type:"resource.bytesMany.result",id:e.id,items:Array.isArray(e.urls)?e.urls.map(t=>({url:t,ok:!0,blob:new Blob([]),mime:"application/octet-stream"})):[]}),"resource.cancel":c,"cvm.discover":e=>n({type:"cvm.discover.result",id:e.id,servers:[]}),"cvm.request":e=>n({type:"cvm.request.result",id:e.id,message:{}}),"cvm.close":e=>n({type:"cvm.close.result",id:e.id}),"outbox.query":e=>n({type:"outbox.query.result",id:e.id,events:[],relays:{}}),"outbox.subscribe":e=>n({type:"outbox.eose",subId:e.subId}),"outbox.close":c,"outbox.publish":e=>n({type:"outbox.publish.result",id:e.id,ok:!0}),"outbox.resolveRelays":e=>n({type:"outbox.resolveRelays.result",id:e.id,plan:{}}),"upload.upload":e=>n({type:"upload.upload.result",id:e.id,result:{url:J}}),"upload.status":e=>n({type:"upload.status.result",id:e.id,status:{}}),"intent.invoke":e=>n({type:"intent.invoke.result",id:e.id,result:{ok:!0,archetype:"reference",action:"open",handled:!0}}),"intent.available":e=>n({type:"intent.available.result",id:e.id,availability:{archetype:e.archetype,available:!0,candidates:[{dTag:"reference-handler",actions:["open"],protocols:["NAP-4"],contracts:[{action:"open",protocol:"NAP-4",eventKinds:[1]}],isDefault:!0}],hasDefault:!0}}),"intent.handlers":e=>n({type:"intent.handlers.result",id:e.id,handlers:[]}),"ble.open":e=>n({type:"ble.open.result",id:e.id,session:{id:"ble-reference",state:"open",device:{id:"reference-device",name:"Reference BLE"}}}),"ble.services":e=>n({type:"ble.services.result",id:e.id,services:[]}),"ble.read":e=>n({type:"ble.read.result",id:e.id,data:[]}),"ble.write":e=>n({type:"ble.write.result",id:e.id}),"ble.subscribe":e=>n({type:"ble.subscribe.result",id:e.id}),"ble.unsubscribe":e=>n({type:"ble.unsubscribe.result",id:e.id}),"ble.close":e=>n({type:"ble.close.result",id:e.id}),"common.encodeNip19":e=>n({type:"common.encodeNip19.result",id:e.id,ok:!0,value:"npub1reference",nip19Type:"npub"}),"common.decodeNip19":e=>n({type:"common.decodeNip19.result",id:e.id,ok:!0,nip19Type:"npub",hex:w}),"common.getProfile":e=>n({type:"common.getProfile.result",id:e.id,ok:!0,pubkey:w,profile:null}),"common.follows":e=>n({type:"common.follows.result",id:e.id,ok:!0,pubkeys:[]}),"common.follow":e=>n({type:"common.follow.result",id:e.id,ok:!0}),"common.unfollow":e=>n({type:"common.unfollow.result",id:e.id,ok:!0}),"common.react":e=>n({type:"common.react.result",id:e.id,ok:!0,eventId:"0".repeat(64)}),"common.report":e=>n({type:"common.report.result",id:e.id,ok:!0,eventId:"1".repeat(64)}),"webrtc.open":e=>n({type:"webrtc.open.result",id:e.id,session:{id:"webrtc-reference",scope:{type:"direct",pubkey:w},channel:"default",state:"connecting"}}),"webrtc.send":e=>n({type:"webrtc.send.result",id:e.id}),"webrtc.close":e=>n({type:"webrtc.close.result",id:e.id}),"link.open":e=>n({type:"link.open.result",id:e.id,status:"opened"}),"lists.supported":e=>n({type:"lists.supported.result",id:e.id,lists:[]}),"lists.add":e=>n({type:"lists.add.result",id:e.id,ok:!0,added:0,skipped:0}),"lists.remove":e=>n({type:"lists.remove.result",id:e.id,ok:!0,removed:0,skipped:0}),"serial.open":e=>n({type:"serial.open.result",id:e.id,session:{id:`serial-${String(e.id)}`,state:"open"}}),"serial.write":e=>n({type:"serial.write.result",id:e.id}),"serial.close":e=>n({type:"serial.close.result",id:e.id})};function W(e={}){const t=e.now??(()=>Date.now()),r={domains:e.capabilities?.domains??[...L],protocols:e.capabilities?.protocols??{}},l=e.services??[],s=[];function o(a){const p=a&&typeof a=="object"&&typeof a.type=="string"?a.type:void 0;if(p==="shell.ready")return[{type:"shell.init",capabilities:{domains:r.domains,protocols:r.protocols},services:l}];if(s.push({envelope:a,verdict:Z(a),timestamp:t()}),!p)return[];const d=V[p];return d?d(a):[]}return{capabilities:r,services:l,get records(){return s},handle:o,reset(){s.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 R(e){return new Promise(t=>setTimeout(t,e))}async function x(e,t,r,l){const s=W(e?{capabilities:e}:void 0),o=r.createElement("iframe");o.setAttribute("sandbox",t.allowSameOrigin?"allow-scripts allow-same-origin":"allow-scripts"),o.setAttribute("aria-hidden","true"),o.style.position="absolute",o.style.width="1px",o.style.height="1px",o.style.left="-9999px";let a;const p=new Promise(g=>{a=g}),d=g=>{if(g.source!==o.contentWindow)return;const H=s.handle(g.data),F=g.source;for(const _ of H)F?.postMessage(_,"*");const S=g.data;S&&typeof S=="object"&&S.type==="shell.ready"&&a()};l.addEventListener("message",d),(r.body??r.documentElement).appendChild(o),o.setAttribute("src",t.url);let m=!1,v=null;const O=Symbol("timeout");await Promise.race([p.then(()=>"ready"),R(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 R(t.settleMs)),l.removeEventListener("message",d);try{o.remove()}catch{}return{installedGlobal:m,bootError:v,emitted:[...s.records]}}async function X(e){const t=e.document??document,r=e.window??window,l={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},s=await x(void 0,l,t,r);let o=null;{const a=await x({domains:[],protocols:{}},l,t,r);o={bootError:a.bootError,emitted:a.emitted}}return{installedGlobal:s.installedGlobal,bootError:s.bootError,emitted:s.emitted,degraded:o}}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 E(e).errors.filter(r=>t.includes(r.code))}function C(e,t){return new RegExp(`name\\s*=\\s*["']${t}["']`,"i").test(e)}function I(e,t,r,l){return{id:e,area:"manifest",severity:"error",title:t,run:s=>{if(l&&!C(s.manifestHtml,l))return u.skip(`No ${l} declared`);const o=ie(s.manifestHtml,r);return o.length?u.fail(o[0].message,o):u.pass()}}}var se=[I("manifest/napplet-type","Declares a valid napplet-type",["missing-napplet-type","invalid-napplet-type"]),I("manifest/declared-naps","napplet-requires lists only real NAP domains",["unknown-required-nap"],"napplet-requires"),I("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(l=>!l.verdict.ok);if(t.length===0)return u.pass(`${e.emitted.length} envelope(s) valid`);const r=t.map(l=>({envelope:l.envelope,errors:l.verdict.errors}));return u.fail(`${t.length}/${e.emitted.length} emitted envelope(s) invalid`,r)}},{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(!C(e.manifestHtml,"napplet-requires"))return u.skip("No napplet-requires to compare against");const t=new Set(E(e.manifestHtml).requires.map(s=>s.startsWith("nap:")?s.slice(4):s)),l=[...new Set(e.emitted.map(s=>s.verdict.domain).filter(s=>typeof s=="string"))].filter(s=>!t.has(s));return l.length?u.fail(`Emitted undeclared NAP domain(s): ${l.join(", ")}`,l):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 r=t.now??(()=>Date.now()),l=t.checks??se,s=r(),o=l.map(d=>{const m=d.run(e);return{id:d.id,area:d.area,severity:d.severity,title:d.title,status:m.status,detail:m.detail,diagnostics:m.diagnostics}}),a={total:o.length,passed:o.filter(d=>d.status==="pass").length,failed:o.filter(d=>d.status==="fail").length,skipped:o.filter(d=>d.status==="skip").length,errors:o.filter(d=>d.status==="fail"&&d.severity==="error").length,warnings:o.filter(d=>d.status==="fail"&&d.severity==="warning").length};return{napplet:E(e.manifestHtml).nappletType,startedAt:s,finishedAt:r(),checks:o,summary:a,ok:a.errors===0}}const ne=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,oe="/__conformance__/events",T=new URLSearchParams(location.search),D=T.get("live")==="1";function y(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const b=document.getElementById("app");function de(){return b.innerHTML=`
2
+ <header class="masthead">
3
+ <h1>napplet conformance ${D?'<span class="live-badge" id="live-badge">● live</span>':""}</h1>
4
+ <p>Load a napplet by URL and run NAP protocol conformance live. Same engine as <code>napplet-conformance</code> CI.</p>
5
+ </header>
6
+ <form id="run-form" class="runbar">
7
+ <input id="url" type="url" placeholder="https://localhost:5173/ (a napplet served with permissive CORS)" autocomplete="off" />
8
+ <button id="run" type="submit">Run conformance</button>
9
+ <button id="rerun" type="button" class="secondary" hidden>Re-run</button>
10
+ </form>
11
+ <p id="status" class="status" role="status"></p>
12
+ <section id="output" class="output"></section>
13
+ `,{urlInput:b.querySelector("#url"),runButton:b.querySelector("#run"),rerunButton:b.querySelector("#rerun"),liveBadge:b.querySelector("#live-badge")??document.createElement("span"),status:b.querySelector("#status"),output:b.querySelector("#output")}}const f=de();let h="",j=0;function k(e,t="info"){f.status.textContent=e,f.status.dataset.kind=t}function A(e){return String(e).padStart(2,"0")}function le(e){const t=new Date(e);return`${A(t.getHours())}:${A(t.getMinutes())}:${A(t.getSeconds())}`}async function ae(e){try{const r=await(await fetch(e,{mode:"cors"})).text();return{html:r,forbidden:ne.test(r)?["window.nostr"]:[],fetched:!0}}catch{return{html:"",forbidden:[],fetched:!1}}}function ue(e){const t=e.ok?"conformant":"non-conformant",l=[...new Set(e.checks.map(a=>a.area))].map(a=>{const p=e.checks.filter(d=>d.area===a).map(d=>{const m=d.severity==="warning"&&d.status==="fail"?' <span class="tag warn">warning</span>':"",v=d.detail?`<span class="detail">${y(d.detail)}</span>`:"";return`<li class="check ${d.status}"><span class="badge ${d.status}">${d.status}</span><span class="id">${y(d.id)}</span>${m}${v}</li>`}).join("");return`<div class="area"><h3>${y(a)}</h3><ul>${p}</ul></div>`}).join(""),s=e.summary,o=j>1?`<span class="run-stamp">run #${j} · ${le(e.finishedAt)}</span>`:"";return`
14
+ <div class="verdict ${t}">
15
+ <span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
16
+ <span class="counts">${s.passed} passed · ${s.failed} failed · ${s.skipped} skipped${s.warnings?` · ${s.warnings} warning(s)`:""}</span>
17
+ ${o}
18
+ </div>
19
+ <div class="checks">${l}</div>
20
+ `}function ce(e){return e.length===0?'<p class="empty">No envelopes were emitted.</p>':`<ul class="envlog">${e.map(r=>{const l=r.verdict.ok?"ok":"bad",s=r.verdict.ok?"":`<div class="errs">${r.verdict.errors.map(o=>y(`${o.code}: ${o.message}`)).join("<br>")}</div>`;return`<li class="env ${l}"><code>${y(JSON.stringify(r.envelope))}</code>${s}</li>`}).join("")}</ul>`}function pe(e,t){if(!t)return`<p class="empty">Couldn't fetch the napplet HTML (likely CORS). Serve it with <code>Access-Control-Allow-Origin: *</code>, or use the CLI for the authoritative manifest checks.</p>`;const r=E(e),l=(s,o)=>`<tr><th>${y(s)}</th><td>${o?y(String(o)):"<em>—</em>"}</td></tr>`;return`
21
+ <table class="manifest">
22
+ ${l("napplet-type",r.nappletType)}
23
+ ${l("requires",r.requires.join(", "))}
24
+ </table>
25
+ ${r.errors.length?`<div class="errs">${r.errors.map(s=>y(`${s.code}: ${s.message}`)).join("<br>")}</div>`:""}
26
+ `}function fe(e,t,r,l){f.output.innerHTML=`
27
+ ${ue(e)}
28
+ <div class="panels">
29
+ <section class="panel"><h2>Emitted envelopes (${t.length})</h2>${ce(t)}</section>
30
+ <section class="panel"><h2>Manifest</h2>${pe(r,l)}</section>
31
+ </div>
32
+ `}async function $(e){h=e,f.runButton.disabled=!0,f.rerunButton.disabled=!0;try{k(`Fetching manifest for ${e} …`);const{html:t,forbidden:r,fetched:l}=await ae(e);k("Booting napplet in a sandboxed iframe …");const s=await X({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),o=Y({manifestHtml:t,boot:s,forbiddenGlobals:r}),a=re(o);j+=1,k(a.ok?"Done — conformant.":"Done — non-conformant.",a.ok?"info":"error"),fe(a,s.emitted,t,l),f.rerunButton.hidden=!1}catch(t){k(`Error: ${t instanceof Error?t.message:String(t)}`,"error")}finally{f.runButton.disabled=!1,f.rerunButton.disabled=!1}}function me(){if(!D||typeof EventSource>"u")return;let e=!1;const t=new EventSource(oe);t.addEventListener("rerun",()=>{!h||e||(e=!0,setTimeout(()=>{e=!1,$(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 r=new URL(location.href);r.searchParams.set("url",t),history.replaceState(null,"",r),$(t)}});f.rerunButton.addEventListener("click",()=>{h&&$(h)});me();const P=T.get("url");P&&(f.urlInput.value=P,$(P));
@@ -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-AIV7w5zv.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-_DMnN297.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.8",
3
+ "version": "0.2.9",
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.8.0"
18
+ "@napplet/conformance": "^0.9.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.8"
24
+ "@napplet/conformance-web": "0.0.9"
25
25
  },
26
26
  "license": "MIT",
27
27
  "repository": {
@@ -1,32 +0,0 @@
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"]'))d(i);new MutationObserver(i=>{for(const o of i)if(o.type==="childList")for(const a of o.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&d(a)}).observe(document,{childList:!0,subtree:!0});function s(i){const o={};return i.integrity&&(o.integrity=i.integrity),i.referrerPolicy&&(o.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?o.credentials="include":i.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function d(i){if(i.ep)return;i.ep=!0;const o=s(i);fetch(i.href,o)}})();var L=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent","ble","webrtc","link","lists","serial","common"],G=["shell"],r={id:"string"},K={"shell.ready":{dir:"out"},"shell.init":{dir:"in"},"relay.subscribe":{dir:"out",fields:{...r,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...r,subId:"string"}},"relay.publish":{dir:"out",fields:{...r,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...r,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...r,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:{...r}},"identity.getRelays":{dir:"out",fields:{...r}},"identity.getProfile":{dir:"out",fields:{...r}},"identity.getFollows":{dir:"out",fields:{...r}},"identity.getList":{dir:"out",fields:{...r,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...r}},"identity.getMutes":{dir:"out",fields:{...r}},"identity.getBlocked":{dir:"out",fields:{...r}},"identity.getBadges":{dir:"out",fields:{...r}},"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:{...r,key:"string"}},"storage.set":{dir:"out",fields:{...r,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...r,key:"string"}},"storage.keys":{dir:"out",fields:{...r}},"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:{...r,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...r,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...r}},"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:{...r}},"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:{...r,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:{...r,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:{...r,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:{...r}},"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:{...r,schema:"object"}},"config.get":{dir:"out",fields:{...r}},"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:{...r,url:"string"}},"resource.bytesMany":{dir:"out",fields:{...r,urls:"array"}},"resource.cancel":{dir:"out",fields:{...r}},"resource.bytes.result":{dir:"in"},"resource.bytes.error":{dir:"in"},"resource.bytesMany.result":{dir:"in"},"resource.bytesMany.error":{dir:"in"},"cvm.discover":{dir:"out",fields:{...r}},"cvm.request":{dir:"out",fields:{...r,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...r,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:{...r,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...r,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...r,subId:"string"}},"outbox.publish":{dir:"out",fields:{...r,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...r,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:{...r,request:"object"}},"upload.status":{dir:"out",fields:{...r,uploadId:"string"}},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...r,request:"object"}},"intent.available":{dir:"out",fields:{...r,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...r}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"},"ble.open":{dir:"out",fields:{...r,request:"object"}},"ble.services":{dir:"out",fields:{...r,sessionId:"string"}},"ble.read":{dir:"out",fields:{...r,sessionId:"string",target:"object"}},"ble.write":{dir:"out",fields:{...r,sessionId:"string",target:"object",data:"array"}},"ble.subscribe":{dir:"out",fields:{...r,sessionId:"string",target:"object"}},"ble.unsubscribe":{dir:"out",fields:{...r,sessionId:"string",target:"object"}},"ble.close":{dir:"out",fields:{...r,sessionId:"string"}},"ble.open.result":{dir:"in"},"ble.services.result":{dir:"in"},"ble.read.result":{dir:"in"},"ble.write.result":{dir:"in"},"ble.subscribe.result":{dir:"in"},"ble.unsubscribe.result":{dir:"in"},"ble.close.result":{dir:"in"},"ble.event":{dir:"in"},"common.encodeNip19":{dir:"out",fields:{...r,input:"object"}},"common.decodeNip19":{dir:"out",fields:{...r,value:"string"}},"common.getProfile":{dir:"out",fields:{...r,target:"string"}},"common.follows":{dir:"out",fields:{...r}},"common.follow":{dir:"out",fields:{...r,pubkeys:"array"}},"common.unfollow":{dir:"out",fields:{...r,pubkeys:"array"}},"common.react":{dir:"out",fields:{...r,targetEventId:"string",reaction:"string"}},"common.report":{dir:"out",fields:{...r,target:"object",reason:"string",text:"string"}},"common.encodeNip19.result":{dir:"in"},"common.decodeNip19.result":{dir:"in"},"common.getProfile.result":{dir:"in"},"common.follows.result":{dir:"in"},"common.follow.result":{dir:"in"},"common.unfollow.result":{dir:"in"},"common.react.result":{dir:"in"},"common.report.result":{dir:"in"},"webrtc.open":{dir:"out",fields:{...r,request:"object"}},"webrtc.send":{dir:"out",fields:{...r,sessionId:"string",payload:"present"}},"webrtc.close":{dir:"out",fields:{...r,sessionId:"string"}},"webrtc.open.result":{dir:"in"},"webrtc.send.result":{dir:"in"},"webrtc.close.result":{dir:"in"},"webrtc.event":{dir:"in"},"link.open":{dir:"out",fields:{...r,url:"string"}},"link.open.result":{dir:"in"},"lists.supported":{dir:"out",fields:{...r}},"lists.add":{dir:"out",fields:{...r,list:"object",items:"array"}},"lists.remove":{dir:"out",fields:{...r,list:"object",items:"array"}},"lists.supported.result":{dir:"in"},"lists.add.result":{dir:"in"},"lists.remove.result":{dir:"in"},"serial.open":{dir:"out",fields:{...r,request:"object"}},"serial.write":{dir:"out",fields:{...r,sessionId:"string",data:"array"}},"serial.close":{dir:"out",fields:{...r,sessionId:"string"}},"serial.open.result":{dir:"in"},"serial.write.result":{dir:"in"},"serial.close.result":{dir:"in"},"serial.event":{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 U(e,t){return t==="present"?e!=null:B(e)===t}function Z(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,d=s.type;if(typeof d!="string")return{ok:!1,errors:[{code:"missing-type",message:"Envelope is missing a string `type` field"}]};const i=d.indexOf(".");if(i<=0)return{ok:!1,type:d,errors:[{code:"malformed-type",message:`Envelope type "${d}" is not in domain.action form`}]};const o=d.slice(0,i);if(!(L.includes(o)||G.includes(o)))return{ok:!1,type:d,domain:o,errors:[{code:"unknown-domain",message:`"${o}" is not a known NAP domain`}]};const p=K[d];if(!p)return{ok:!1,type:d,domain:o,errors:[{code:"unknown-type",message:`"${d}" is not a known ${o} message type`}]};if(p.dir==="in")return{ok:!1,type:d,domain:o,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${d}" is a shell→napplet message; a napplet must not emit it`}]};for(const[l,m]of Object.entries(p.fields??{})){if(!(l in s)||s[l]===void 0){t.push({code:"missing-field",message:`Required field "${l}" is missing`,field:l});continue}U(s[l],m)||t.push({code:"wrong-type",message:`Field "${l}" should be ${m} but is ${B(s[l])}`,field:l})}return{ok:t.length===0,type:d,domain:o,direction:"out",errors:t}}var M=/^[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 N(e,t){const s=/<meta\b[^>]*>/gi;let d;for(;(d=s.exec(e))!==null;){const i=d[0],o=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);if((o?.[1]??o?.[2]??"").trim()!==t)continue;const p=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);return z((p?.[1]??p?.[2]??"").trim())}}function q(e){if(Array.isArray(e))return e.some(q);if(e&&typeof e=="object"){const t=e;return"pattern"in t?!0:Object.values(t).some(q)}return!1}function E(e,t={}){const s=[],d=[],i=N(e,"napplet-type");i?M.test(i)||s.push({code:"invalid-napplet-type",message:`napplet-type "${i}" must match ${M}`}):s.push({code:"missing-napplet-type",message:'No <meta name="napplet-type"> declared'});const o=N(e,"napplet-requires"),a=o?o.split(",").map(l=>l.trim()).filter(Boolean):[];for(const l of a){const m=l.startsWith("nap:")?l.slice(4):l;L.includes(m)||s.push({code:"unknown-required-nap",message:`napplet-requires lists "${l}" which is not a known NAP domain`})}const p=N(e,"napplet-config-schema");if(p){let l;try{l=JSON.parse(p)}catch{l=void 0,s.push({code:"invalid-config-schema",message:"napplet-config-schema is not valid JSON"})}l!==void 0&&(typeof l!="object"||l===null||Array.isArray(l)?s.push({code:"invalid-config-schema",message:"napplet-config-schema must be a JSON Schema object"}):q(l)&&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:a,errors:s,warnings:d}}var w="f".repeat(64),J="https://reference.invalid/blob",n=e=>[e],c=()=>[],V={"relay.subscribe":e=>n({type:"relay.eose",subId:e.subId}),"relay.close":c,"relay.publish":e=>n({type:"relay.publish.result",id:e.id,ok:!0,event:e.event}),"relay.publishEncrypted":e=>n({type:"relay.publishEncrypted.result",id:e.id,ok:!0,event:e.event}),"relay.query":e=>n({type:"relay.query.result",id:e.id,events:[]}),"identity.getPublicKey":e=>n({type:"identity.getPublicKey.result",id:e.id,pubkey:w}),"identity.getRelays":e=>n({type:"identity.getRelays.result",id:e.id,relays:{}}),"identity.getProfile":e=>n({type:"identity.getProfile.result",id:e.id,profile:null}),"identity.getFollows":e=>n({type:"identity.getFollows.result",id:e.id,pubkeys:[]}),"identity.getList":e=>n({type:"identity.getList.result",id:e.id,entries:[]}),"identity.getZaps":e=>n({type:"identity.getZaps.result",id:e.id,zaps:[]}),"identity.getMutes":e=>n({type:"identity.getMutes.result",id:e.id,pubkeys:[]}),"identity.getBlocked":e=>n({type:"identity.getBlocked.result",id:e.id,pubkeys:[]}),"identity.getBadges":e=>n({type:"identity.getBadges.result",id:e.id,badges:[]}),"storage.get":e=>n({type:"storage.get.result",id:e.id,value:null}),"storage.set":e=>n({type:"storage.set.result",id:e.id}),"storage.remove":e=>n({type:"storage.remove.result",id:e.id}),"storage.keys":e=>n({type:"storage.keys.result",id:e.id,keys:[]}),"inc.emit":c,"inc.subscribe":e=>n({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":c,"inc.channel.open":e=>n({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=>n({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":c,"theme.get":e=>n({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":c,"keys.registerAction":e=>n({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":c,"media.session.create":e=>n({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=>n({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=>n({type:"notify.permission.result",id:e.id,granted:!0}),"config.registerSchema":e=>n({type:"config.registerSchema.result",id:e.id,ok:!0}),"config.get":e=>n({type:"config.values",id:e.id,values:{}}),"config.subscribe":()=>n({type:"config.values",values:{}}),"config.unsubscribe":c,"config.openSettings":c,"resource.bytes":e=>n({type:"resource.bytes.result",id:e.id,blob:new Blob([]),mime:"application/octet-stream"}),"resource.bytesMany":e=>n({type:"resource.bytesMany.result",id:e.id,items:Array.isArray(e.urls)?e.urls.map(t=>({url:t,ok:!0,blob:new Blob([]),mime:"application/octet-stream"})):[]}),"resource.cancel":c,"cvm.discover":e=>n({type:"cvm.discover.result",id:e.id,servers:[]}),"cvm.request":e=>n({type:"cvm.request.result",id:e.id,message:{}}),"cvm.close":e=>n({type:"cvm.close.result",id:e.id}),"outbox.query":e=>n({type:"outbox.query.result",id:e.id,events:[],relays:{}}),"outbox.subscribe":e=>n({type:"outbox.eose",subId:e.subId}),"outbox.close":c,"outbox.publish":e=>n({type:"outbox.publish.result",id:e.id,ok:!0}),"outbox.resolveRelays":e=>n({type:"outbox.resolveRelays.result",id:e.id,plan:{}}),"upload.upload":e=>n({type:"upload.upload.result",id:e.id,result:{url:J}}),"upload.status":e=>n({type:"upload.status.result",id:e.id,status:{}}),"intent.invoke":e=>n({type:"intent.invoke.result",id:e.id,result:{ok:!0,archetype:"reference",action:"open",handled:!0}}),"intent.available":e=>n({type:"intent.available.result",id:e.id,availability:{archetype:e.archetype,available:!0,candidates:[{dTag:"reference-handler",actions:["open"],protocols:["NAP-4"],contracts:[{action:"open",protocol:"NAP-4",eventKinds:[1]}],isDefault:!0}],hasDefault:!0}}),"intent.handlers":e=>n({type:"intent.handlers.result",id:e.id,handlers:[]}),"ble.open":e=>n({type:"ble.open.result",id:e.id,session:{id:"ble-reference",state:"open",device:{id:"reference-device",name:"Reference BLE"}}}),"ble.services":e=>n({type:"ble.services.result",id:e.id,services:[]}),"ble.read":e=>n({type:"ble.read.result",id:e.id,data:[]}),"ble.write":e=>n({type:"ble.write.result",id:e.id}),"ble.subscribe":e=>n({type:"ble.subscribe.result",id:e.id}),"ble.unsubscribe":e=>n({type:"ble.unsubscribe.result",id:e.id}),"ble.close":e=>n({type:"ble.close.result",id:e.id}),"common.encodeNip19":e=>n({type:"common.encodeNip19.result",id:e.id,ok:!0,value:"npub1reference",nip19Type:"npub"}),"common.decodeNip19":e=>n({type:"common.decodeNip19.result",id:e.id,ok:!0,nip19Type:"npub",hex:w}),"common.getProfile":e=>n({type:"common.getProfile.result",id:e.id,ok:!0,pubkey:w,profile:null}),"common.follows":e=>n({type:"common.follows.result",id:e.id,ok:!0,pubkeys:[]}),"common.follow":e=>n({type:"common.follow.result",id:e.id,ok:!0}),"common.unfollow":e=>n({type:"common.unfollow.result",id:e.id,ok:!0}),"common.react":e=>n({type:"common.react.result",id:e.id,ok:!0,eventId:"0".repeat(64)}),"common.report":e=>n({type:"common.report.result",id:e.id,ok:!0,eventId:"1".repeat(64)}),"webrtc.open":e=>n({type:"webrtc.open.result",id:e.id,session:{id:"webrtc-reference",scope:{type:"direct",pubkey:w},channel:"default",state:"connecting"}}),"webrtc.send":e=>n({type:"webrtc.send.result",id:e.id}),"webrtc.close":e=>n({type:"webrtc.close.result",id:e.id}),"link.open":e=>n({type:"link.open.result",id:e.id,status:"opened"}),"lists.supported":e=>n({type:"lists.supported.result",id:e.id,lists:[]}),"lists.add":e=>n({type:"lists.add.result",id:e.id,ok:!0,added:0,skipped:0}),"lists.remove":e=>n({type:"lists.remove.result",id:e.id,ok:!0,removed:0,skipped:0}),"serial.open":e=>n({type:"serial.open.result",id:e.id,session:{id:`serial-${String(e.id)}`,state:"open"}}),"serial.write":e=>n({type:"serial.write.result",id:e.id}),"serial.close":e=>n({type:"serial.close.result",id:e.id})};function W(e={}){const t=e.now??(()=>Date.now()),s={domains:e.capabilities?.domains??[...L],protocols:e.capabilities?.protocols??{}},d=e.services??[],i=[];function o(a){const p=a&&typeof a=="object"&&typeof a.type=="string"?a.type:void 0;if(p==="shell.ready")return[{type:"shell.init",capabilities:{domains:s.domains,protocols:s.protocols},services:d}];if(i.push({envelope:a,verdict:Z(a),timestamp:t()}),!p)return[];const l=V[p];return l?l(a):[]}return{capabilities:s,services:d,get records(){return i},handle:o,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 R(e){return new Promise(t=>setTimeout(t,e))}async function x(e,t,s,d){const i=W(e?{capabilities:e}:void 0),o=s.createElement("iframe");o.setAttribute("sandbox",t.allowSameOrigin?"allow-scripts allow-same-origin":"allow-scripts"),o.setAttribute("aria-hidden","true"),o.style.position="absolute",o.style.width="1px",o.style.height="1px",o.style.left="-9999px";let a;const p=new Promise(g=>{a=g}),l=g=>{if(g.source!==o.contentWindow)return;const H=i.handle(g.data),F=g.source;for(const _ of H)F?.postMessage(_,"*");const S=g.data;S&&typeof S=="object"&&S.type==="shell.ready"&&a()};d.addEventListener("message",l),(s.body??s.documentElement).appendChild(o),o.setAttribute("src",t.url);let m=!1,v=null;const O=Symbol("timeout");await Promise.race([p.then(()=>"ready"),R(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 R(t.settleMs)),d.removeEventListener("message",l);try{o.remove()}catch{}return{installedGlobal:m,bootError:v,emitted:[...i.records]}}async function X(e){const t=e.document??document,s=e.window??window,d={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},i=await x(void 0,d,t,s);let o=null;{const a=await x({domains:[],protocols:{}},d,t,s);o={bootError:a.bootError,emitted:a.emitted}}return{installedGlobal:i.installedGlobal,bootError:i.bootError,emitted:i.emitted,degraded:o}}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 E(e).errors.filter(s=>t.includes(s.code))}function C(e,t){return new RegExp(`name\\s*=\\s*["']${t}["']`,"i").test(e)}function I(e,t,s,d){return{id:e,area:"manifest",severity:"error",title:t,run:i=>{if(d&&!C(i.manifestHtml,d))return u.skip(`No ${d} declared`);const o=ie(i.manifestHtml,s);return o.length?u.fail(o[0].message,o):u.pass()}}}var re=[I("manifest/napplet-type","Declares a valid napplet-type",["missing-napplet-type","invalid-napplet-type"]),I("manifest/declared-naps","napplet-requires lists only real NAP domains",["unknown-required-nap"],"napplet-requires"),I("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(d=>!d.verdict.ok);if(t.length===0)return u.pass(`${e.emitted.length} envelope(s) valid`);const s=t.map(d=>({envelope:d.envelope,errors:d.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(!C(e.manifestHtml,"napplet-requires"))return u.skip("No napplet-requires to compare against");const t=new Set(E(e.manifestHtml).requires.map(i=>i.startsWith("nap:")?i.slice(4):i)),d=[...new Set(e.emitted.map(i=>i.verdict.domain).filter(i=>typeof i=="string"))].filter(i=>!t.has(i));return d.length?u.fail(`Emitted undeclared NAP domain(s): ${d.join(", ")}`,d):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 se(e,t={}){const s=t.now??(()=>Date.now()),d=t.checks??re,i=s(),o=d.map(l=>{const m=l.run(e);return{id:l.id,area:l.area,severity:l.severity,title:l.title,status:m.status,detail:m.detail,diagnostics:m.diagnostics}}),a={total:o.length,passed:o.filter(l=>l.status==="pass").length,failed:o.filter(l=>l.status==="fail").length,skipped:o.filter(l=>l.status==="skip").length,errors:o.filter(l=>l.status==="fail"&&l.severity==="error").length,warnings:o.filter(l=>l.status==="fail"&&l.severity==="warning").length};return{napplet:E(e.manifestHtml).nappletType,startedAt:i,finishedAt:s(),checks:o,summary:a,ok:a.errors===0}}const ne=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,oe="/__conformance__/events",T=new URLSearchParams(location.search),D=T.get("live")==="1";function y(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const b=document.getElementById("app");function le(){return b.innerHTML=`
2
- <header class="masthead">
3
- <h1>napplet conformance ${D?'<span class="live-badge" id="live-badge">● live</span>':""}</h1>
4
- <p>Load a napplet by URL and run NAP protocol conformance live. Same engine as <code>napplet-conformance</code> CI.</p>
5
- </header>
6
- <form id="run-form" class="runbar">
7
- <input id="url" type="url" placeholder="https://localhost:5173/ (a napplet served with permissive CORS)" autocomplete="off" />
8
- <button id="run" type="submit">Run conformance</button>
9
- <button id="rerun" type="button" class="secondary" hidden>Re-run</button>
10
- </form>
11
- <p id="status" class="status" role="status"></p>
12
- <section id="output" class="output"></section>
13
- `,{urlInput:b.querySelector("#url"),runButton:b.querySelector("#run"),rerunButton:b.querySelector("#rerun"),liveBadge:b.querySelector("#live-badge")??document.createElement("span"),status:b.querySelector("#status"),output:b.querySelector("#output")}}const f=le();let h="",j=0;function k(e,t="info"){f.status.textContent=e,f.status.dataset.kind=t}function A(e){return String(e).padStart(2,"0")}function de(e){const t=new Date(e);return`${A(t.getHours())}:${A(t.getMinutes())}:${A(t.getSeconds())}`}async function ae(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",d=[...new Set(e.checks.map(a=>a.area))].map(a=>{const p=e.checks.filter(l=>l.area===a).map(l=>{const m=l.severity==="warning"&&l.status==="fail"?' <span class="tag warn">warning</span>':"",v=l.detail?`<span class="detail">${y(l.detail)}</span>`:"";return`<li class="check ${l.status}"><span class="badge ${l.status}">${l.status}</span><span class="id">${y(l.id)}</span>${m}${v}</li>`}).join("");return`<div class="area"><h3>${y(a)}</h3><ul>${p}</ul></div>`}).join(""),i=e.summary,o=j>1?`<span class="run-stamp">run #${j} · ${de(e.finishedAt)}</span>`:"";return`
14
- <div class="verdict ${t}">
15
- <span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
16
- <span class="counts">${i.passed} passed · ${i.failed} failed · ${i.skipped} skipped${i.warnings?` · ${i.warnings} warning(s)`:""}</span>
17
- ${o}
18
- </div>
19
- <div class="checks">${d}</div>
20
- `}function ce(e){return e.length===0?'<p class="empty">No envelopes were emitted.</p>':`<ul class="envlog">${e.map(s=>{const d=s.verdict.ok?"ok":"bad",i=s.verdict.ok?"":`<div class="errs">${s.verdict.errors.map(o=>y(`${o.code}: ${o.message}`)).join("<br>")}</div>`;return`<li class="env ${d}"><code>${y(JSON.stringify(s.envelope))}</code>${i}</li>`}).join("")}</ul>`}function pe(e,t){if(!t)return`<p class="empty">Couldn't fetch the napplet HTML (likely CORS). Serve it with <code>Access-Control-Allow-Origin: *</code>, or use the CLI for the authoritative manifest checks.</p>`;const s=E(e),d=(i,o)=>`<tr><th>${y(i)}</th><td>${o?y(String(o)):"<em>—</em>"}</td></tr>`;return`
21
- <table class="manifest">
22
- ${d("napplet-type",s.nappletType)}
23
- ${d("requires",s.requires.join(", "))}
24
- </table>
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,d){f.output.innerHTML=`
27
- ${ue(e)}
28
- <div class="panels">
29
- <section class="panel"><h2>Emitted envelopes (${t.length})</h2>${ce(t)}</section>
30
- <section class="panel"><h2>Manifest</h2>${pe(s,d)}</section>
31
- </div>
32
- `}async function $(e){h=e,f.runButton.disabled=!0,f.rerunButton.disabled=!0;try{k(`Fetching manifest for ${e} …`);const{html:t,forbidden:s,fetched:d}=await ae(e);k("Booting napplet in a sandboxed iframe …");const i=await X({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),o=Y({manifestHtml:t,boot:i,forbiddenGlobals:s}),a=se(o);j+=1,k(a.ok?"Done — conformant.":"Done — non-conformant.",a.ok?"info":"error"),fe(a,i.emitted,t,d),f.rerunButton.hidden=!1}catch(t){k(`Error: ${t instanceof Error?t.message:String(t)}`,"error")}finally{f.runButton.disabled=!1,f.rerunButton.disabled=!1}}function me(){if(!D||typeof EventSource>"u")return;let e=!1;const t=new EventSource(oe);t.addEventListener("rerun",()=>{!h||e||(e=!0,setTimeout(()=>{e=!1,$(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),$(t)}});f.rerunButton.addEventListener("click",()=>{h&&$(h)});me();const P=T.get("url");P&&(f.urlInput.value=P,$(P));