@napplet/conformance-cli 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -133,9 +133,12 @@ var NappletConformanceHostBundle = (() => {
133
133
  "config.values": { dir: "in" },
134
134
  "config.schemaError": { dir: "in" },
135
135
  // ── resource ─────────────────────────────────────────────────────────────
136
+ "resource.info": { dir: "out", fields: { ...ID } },
136
137
  "resource.bytes": { dir: "out", fields: { ...ID, url: "string" } },
137
138
  "resource.bytesMany": { dir: "out", fields: { ...ID, urls: "array" } },
138
139
  "resource.cancel": { dir: "out", fields: { ...ID } },
140
+ "resource.info.result": { dir: "in" },
141
+ "resource.info.error": { dir: "in" },
139
142
  "resource.bytes.result": { dir: "in" },
140
143
  "resource.bytes.error": { dir: "in" },
141
144
  "resource.bytesMany.result": { dir: "in" },
@@ -149,11 +152,13 @@ var NappletConformanceHostBundle = (() => {
149
152
  "cvm.close.result": { dir: "in" },
150
153
  "cvm.event": { dir: "in" },
151
154
  // ── outbox ───────────────────────────────────────────────────────────────
155
+ "outbox.getEvent": { dir: "out", fields: { ...ID, eventId: "string" } },
152
156
  "outbox.query": { dir: "out", fields: { ...ID, filters: "present" } },
153
157
  "outbox.subscribe": { dir: "out", fields: { ...ID, subId: "string", filters: "present" } },
154
158
  "outbox.close": { dir: "out", fields: { ...ID, subId: "string" } },
155
159
  "outbox.publish": { dir: "out", fields: { ...ID, event: "object" } },
156
160
  "outbox.resolveRelays": { dir: "out", fields: { ...ID, target: "present" } },
161
+ "outbox.getEvent.result": { dir: "in" },
157
162
  "outbox.query.result": { dir: "in" },
158
163
  "outbox.event": { dir: "in" },
159
164
  "outbox.eose": { dir: "in" },
@@ -161,8 +166,10 @@ var NappletConformanceHostBundle = (() => {
161
166
  "outbox.publish.result": { dir: "in" },
162
167
  "outbox.resolveRelays.result": { dir: "in" },
163
168
  // ── upload ───────────────────────────────────────────────────────────────
169
+ "upload.info": { dir: "out", fields: { ...ID } },
164
170
  "upload.upload": { dir: "out", fields: { ...ID, request: "object" } },
165
171
  "upload.status": { dir: "out", fields: { ...ID, uploadId: "string" } },
172
+ "upload.info.result": { dir: "in" },
166
173
  "upload.upload.result": { dir: "in" },
167
174
  "upload.status.result": { dir: "in" },
168
175
  "upload.status.changed": { dir: "in" },
@@ -454,18 +461,39 @@ var NappletConformanceHostBundle = (() => {
454
461
  mime: "application/octet-stream"
455
462
  })) : []
456
463
  }),
464
+ "resource.info": (e) => ok({
465
+ type: "resource.info.result",
466
+ id: e.id,
467
+ info: {
468
+ schemes: [
469
+ { scheme: "data", enabled: true },
470
+ { scheme: "https", enabled: true }
471
+ ]
472
+ }
473
+ }),
457
474
  "resource.cancel": none,
458
475
  // cvm
459
476
  "cvm.discover": (e) => ok({ type: "cvm.discover.result", id: e.id, servers: [] }),
460
477
  "cvm.request": (e) => ok({ type: "cvm.request.result", id: e.id, message: {} }),
461
478
  "cvm.close": (e) => ok({ type: "cvm.close.result", id: e.id }),
462
479
  // outbox
480
+ "outbox.getEvent": (e) => ok({ type: "outbox.getEvent.result", id: e.id, relays: [] }),
463
481
  "outbox.query": (e) => ok({ type: "outbox.query.result", id: e.id, events: [], relays: {} }),
464
482
  "outbox.subscribe": (e) => ok({ type: "outbox.eose", subId: e.subId }),
465
483
  "outbox.close": none,
466
484
  "outbox.publish": (e) => ok({ type: "outbox.publish.result", id: e.id, ok: true }),
467
485
  "outbox.resolveRelays": (e) => ok({ type: "outbox.resolveRelays.result", id: e.id, plan: {} }),
468
486
  // upload
487
+ "upload.info": (e) => ok({
488
+ type: "upload.info.result",
489
+ id: e.id,
490
+ info: {
491
+ rails: [
492
+ { rail: "nip96", enabled: true, returns: ["https"] },
493
+ { rail: "blossom", enabled: true, returns: ["https", "blossom"] }
494
+ ]
495
+ }
496
+ }),
469
497
  "upload.upload": (e) => ok({ type: "upload.upload.result", id: e.id, result: { url: REFERENCE_BLOB_URL } }),
470
498
  "upload.status": (e) => ok({ type: "upload.status.result", id: e.id, status: {} }),
471
499
  // intent
@@ -0,0 +1,46 @@
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 P=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent","ble","webrtc","link","lists","serial","common","dm"],r={id:"string"},F={"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.info":{dir:"out",fields:{...r}},"resource.bytes":{dir:"out",fields:{...r,url:"string"}},"resource.bytesMany":{dir:"out",fields:{...r,urls:"array"}},"resource.cancel":{dir:"out",fields:{...r}},"resource.info.result":{dir:"in"},"resource.info.error":{dir:"in"},"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.getEvent":{dir:"out",fields:{...r,eventId:"string"}},"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.getEvent.result":{dir:"in"},"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.info":{dir:"out",fields:{...r}},"upload.upload":{dir:"out",fields:{...r,request:"object"}},"upload.status":{dir:"out",fields:{...r,uploadId:"string"}},"upload.info.result":{dir:"in"},"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"},"dm.status":{dir:"out",fields:{...r}},"dm.conversations":{dir:"out",fields:{...r}},"dm.messages":{dir:"out",fields:{...r,conversationId:"string"}},"dm.send":{dir:"out",fields:{...r,recipients:"array",content:"string"}},"dm.subscribe":{dir:"out",fields:{...r}},"dm.unsubscribe":{dir:"out",fields:{...r,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 L(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 G(e,t){return t==="present"?e!=null:L(e)===t}function K(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(!P.includes(o))return{ok:!1,type:d,domain:o,errors:[{code:"unknown-domain",message:`"${o}" is not a known NAP domain`}]};const m=F[d];if(!m)return{ok:!1,type:d,domain:o,errors:[{code:"unknown-type",message:`"${d}" is not a known ${o} message type`}]};if(m.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,y]of Object.entries(m.fields??{})){if(!(l in s)||s[l]===void 0){t.push({code:"missing-field",message:`Required field "${l}" is missing`,field:l});continue}G(s[l],y)||t.push({code:"wrong-type",message:`Field "${l}" should be ${y} but is ${L(s[l])}`,field:l})}return{ok:t.length===0,type:d,domain:o,direction:"out",errors:t}}var x=/^[a-z0-9][a-z0-9._:-]*$/;function U(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 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 m=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);return U((m?.[1]??m?.[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,t={}){const s=[],d=[],i=S(e,"napplet-type");i?x.test(i)||s.push({code:"invalid-napplet-type",message:`napplet-type "${i}" must match ${x}`}):s.push({code:"missing-napplet-type",message:'No <meta name="napplet-type"> declared'});const o=S(e,"napplet-requires"),a=o?o.split(",").map(l=>l.trim()).filter(Boolean):[];for(const l of a){const y=l.startsWith("nap:")?l.slice(4):l;P.includes(y)||s.push({code:"unknown-required-nap",message:`napplet-requires lists "${l}" which is not a known NAP domain`})}const m=S(e,"napplet-config-schema");if(m){let l;try{l=JSON.parse(m)}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=()=>[],W={"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.info":e=>n({type:"resource.info.result",id:e.id,info:{schemes:[{scheme:"data",enabled:!0},{scheme:"https",enabled:!0}]}}),"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.getEvent":e=>n({type:"outbox.getEvent.result",id:e.id,relays:[]}),"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.info":e=>n({type:"upload.info.result",id:e.id,info:{rails:[{rail:"nip96",enabled:!0,returns:["https"]},{rail:"blossom",enabled:!0,returns:["https","blossom"]}]}}),"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 Z(e={}){const t=e.now??(()=>Date.now()),s=[];function d(i){const o=i&&typeof i=="object"&&typeof i.type=="string"?i.type:void 0;if(s.push({envelope:i,verdict:K(i),timestamp:t()}),!o)return[];const a=W[o];return a?a(i):[]}return{get records(){return s},handle:d,reset(){s.length=0}}}function z(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}}var M="__nappletConformance.error";function R(e){return new Promise(t=>setTimeout(t,e))}function V(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function Y(e){return`
2
+ (() => {
3
+ const domains = ${JSON.stringify(e)};
4
+ const napplet = {};
5
+ for (const domain of domains) napplet[domain] = {};
6
+ window.napplet = napplet;
7
+ const report = (reason) => {
8
+ try {
9
+ window.parent.postMessage({ type: '${M}', reason: String(reason && reason.message || reason) }, '*');
10
+ } catch {}
11
+ };
12
+ window.addEventListener('error', (event) => report(event.error || event.message));
13
+ window.addEventListener('unhandledrejection', (event) => report(event.reason));
14
+ })();
15
+ `}function X(e,t,s){const d=`<base href="${V(t)}">`,i=`<script>${Y(s)}<\/script>`,o=/<head(?:\s[^>]*)?>/i.exec(e);if(o){const a=o.index+o[0].length;return`${e.slice(0,a)}${d}${i}${e.slice(a)}`}return`${d}${i}${e}`}function Q(e){if(!e.startsWith("data:"))return null;const t=e.indexOf(",");if(t<0)return"";const s=e.slice(0,t),d=e.slice(t+1);return s.endsWith(";base64")?atob(d):decodeURIComponent(d)}async function ee(e){if(e==="about:blank")return"<!doctype html><html><head></head><body></body></html>";const t=Q(e);if(t!==null)return t;const s=await fetch(e);if(!s.ok)throw new Error(`Failed to fetch napplet HTML: ${s.status} ${s.statusText}`);return s.text()}async function O(e,t,s,d){const i=Z(),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=null;const m=p=>{if(p.source!==o.contentWindow)return;const h=p.data;if(h&&typeof h=="object"&&h.type===M){a=`napplet boot error: ${String(h.reason??"unknown error")}`;return}const D=i.handle(p.data),H=p.source;for(const _ of D)H?.postMessage(_,"*")};let l=!1;const y=new Promise(p=>{o.addEventListener("load",()=>{l=!0,p()},{once:!0})});d.addEventListener("message",m),(s.body??s.documentElement).appendChild(o);try{const p=await ee(t.url);o.srcdoc=X(p,t.url,e)}catch(p){a=p instanceof Error?p.message:String(p)}if(!a){const p=Symbol("timeout");await Promise.race([y.then(()=>"loaded"),R(t.readyTimeoutMs).then(()=>p)])===p||!l?a=`napplet did not boot (iframe did not load within ${t.readyTimeoutMs}ms; a conformant napplet must run under sandbox="allow-scripts" without allow-same-origin)`:await R(t.settleMs)}d.removeEventListener("message",m);try{o.remove()}catch{}return{installedGlobal:a===null,bootError:a,emitted:[...i.records]}}async function te(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 O(P,d,t,s);let o=null;{const a=await O([],d,t,s);o={bootError:a.bootError,emitted:a.emitted}}return{installedGlobal:i.installedGlobal,bootError:i.bootError,emitted:i.emitted,degraded:o}}var ie=e=>({status:"pass",detail:e}),re=(e,t)=>({status:"fail",detail:e,diagnostics:t}),se=e=>({status:"skip",detail:e}),u={pass:ie,fail:re,skip:se};function ne(e,t){return $(e).errors.filter(s=>t.includes(s.code))}function B(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&&!B(i.manifestHtml,d))return u.skip(`No ${d} declared`);const o=ne(i.manifestHtml,s);return o.length?u.fail(o[0].message,o):u.pass()}}}var oe=[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:"Runs after runtime-injected window.napplet is installed",run:e=>e.installedGlobal?u.pass():u.fail("runtime did not inject window.napplet before load")},{id:"boot/no-boot-error",area:"boot",severity:"error",title:"Boots without an uncaught error",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(!B(e.manifestHtml,"napplet-requires"))return u.skip("No napplet-requires to compare against");const t=new Set($(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/domain-absence",area:"degradation",severity:"error",title:"Does not crash when no NAP domains are injected",run:e=>e.degraded?e.degraded.bootError?u.fail(`Crashed with no injected domains: ${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 de(e,t={}){const s=t.now??(()=>Date.now()),d=t.checks??oe,i=s(),o=d.map(l=>{const y=l.run(e);return{id:l.id,area:l.area,severity:l.severity,title:l.title,status:y.status,detail:y.detail,diagnostics:y.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.manifestHtml).nappletType,startedAt:i,finishedAt:s(),checks:o,summary:a,ok:a.errors===0}}const le=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,ae="/__conformance__/events",C=new URLSearchParams(location.search),T=C.get("live")==="1";function b(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const g=document.getElementById("app");function ue(){return g.innerHTML=`
16
+ <header class="masthead">
17
+ <h1>napplet conformance ${T?'<span class="live-badge" id="live-badge">● live</span>':""}</h1>
18
+ <p>Load a napplet by URL and run NAP protocol conformance live. Same engine as <code>napplet-conformance</code> CI.</p>
19
+ </header>
20
+ <form id="run-form" class="runbar">
21
+ <input id="url" type="url" placeholder="https://localhost:5173/ (a napplet served with permissive CORS)" autocomplete="off" />
22
+ <button id="run" type="submit">Run conformance</button>
23
+ <button id="rerun" type="button" class="secondary" hidden>Re-run</button>
24
+ </form>
25
+ <p id="status" class="status" role="status"></p>
26
+ <section id="output" class="output"></section>
27
+ `,{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=ue();let v="",A=0;function k(e,t="info"){f.status.textContent=e,f.status.dataset.kind=t}function N(e){return String(e).padStart(2,"0")}function ce(e){const t=new Date(e);return`${N(t.getHours())}:${N(t.getMinutes())}:${N(t.getSeconds())}`}async function pe(e){try{const s=await(await fetch(e,{mode:"cors"})).text();return{html:s,forbidden:le.test(s)?["window.nostr"]:[],fetched:!0}}catch{return{html:"",forbidden:[],fetched:!1}}}function fe(e){const t=e.ok?"conformant":"non-conformant",d=[...new Set(e.checks.map(a=>a.area))].map(a=>{const m=e.checks.filter(l=>l.area===a).map(l=>{const y=l.severity==="warning"&&l.status==="fail"?' <span class="tag warn">warning</span>':"",p=l.detail?`<span class="detail">${b(l.detail)}</span>`:"";return`<li class="check ${l.status}"><span class="badge ${l.status}">${l.status}</span><span class="id">${b(l.id)}</span>${y}${p}</li>`}).join("");return`<div class="area"><h3>${b(a)}</h3><ul>${m}</ul></div>`}).join(""),i=e.summary,o=A>1?`<span class="run-stamp">run #${A} · ${ce(e.finishedAt)}</span>`:"";return`
28
+ <div class="verdict ${t}">
29
+ <span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
30
+ <span class="counts">${i.passed} passed · ${i.failed} failed · ${i.skipped} skipped${i.warnings?` · ${i.warnings} warning(s)`:""}</span>
31
+ ${o}
32
+ </div>
33
+ <div class="checks">${d}</div>
34
+ `}function me(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=>b(`${o.code}: ${o.message}`)).join("<br>")}</div>`;return`<li class="env ${d}"><code>${b(JSON.stringify(s.envelope))}</code>${i}</li>`}).join("")}</ul>`}function ye(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),d=(i,o)=>`<tr><th>${b(i)}</th><td>${o?b(String(o)):"<em>—</em>"}</td></tr>`;return`
35
+ <table class="manifest">
36
+ ${d("napplet-type",s.nappletType)}
37
+ ${d("requires",s.requires.join(", "))}
38
+ </table>
39
+ ${s.errors.length?`<div class="errs">${s.errors.map(i=>b(`${i.code}: ${i.message}`)).join("<br>")}</div>`:""}
40
+ `}function be(e,t,s,d){f.output.innerHTML=`
41
+ ${fe(e)}
42
+ <div class="panels">
43
+ <section class="panel"><h2>Emitted envelopes (${t.length})</h2>${me(t)}</section>
44
+ <section class="panel"><h2>Manifest</h2>${ye(s,d)}</section>
45
+ </div>
46
+ `}async function E(e){v=e,f.runButton.disabled=!0,f.rerunButton.disabled=!0;try{k(`Fetching manifest for ${e} …`);const{html:t,forbidden:s,fetched:d}=await pe(e);k("Booting napplet in a sandboxed iframe …");const i=await te({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),o=z({manifestHtml:t,boot:i,forbiddenGlobals:s}),a=de(o);A+=1,k(a.ok?"Done — conformant.":"Done — non-conformant.",a.ok?"info":"error"),be(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 ge(){if(!T||typeof EventSource>"u")return;let e=!1;const t=new EventSource(ae);t.addEventListener("rerun",()=>{!v||e||(e=!0,setTimeout(()=>{e=!1,E(v)},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",()=>{v&&E(v)});ge();const j=C.get("url");j&&(f.urlInput.value=j,E(j));
@@ -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-BOe5M_Pp.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-y56oSaYx.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.10",
3
+ "version": "0.2.11",
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.9.1"
18
+ "@napplet/conformance": "^0.10.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.10"
24
+ "@napplet/conformance-web": "0.0.11"
25
25
  },
26
26
  "license": "MIT",
27
27
  "repository": {
@@ -1,46 +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 n of i)if(n.type==="childList")for(const a of n.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&d(a)}).observe(document,{childList:!0,subtree:!0});function r(i){const n={};return i.integrity&&(n.integrity=i.integrity),i.referrerPolicy&&(n.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?n.credentials="include":i.crossOrigin==="anonymous"?n.credentials="omit":n.credentials="same-origin",n}function d(i){if(i.ep)return;i.ep=!0;const n=r(i);fetch(i.href,n)}})();var P=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent","ble","webrtc","link","lists","serial","common","dm"],s={id:"string"},F={"relay.subscribe":{dir:"out",fields:{...s,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...s,subId:"string"}},"relay.publish":{dir:"out",fields:{...s,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...s,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...s,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:{...s}},"identity.getRelays":{dir:"out",fields:{...s}},"identity.getProfile":{dir:"out",fields:{...s}},"identity.getFollows":{dir:"out",fields:{...s}},"identity.getList":{dir:"out",fields:{...s,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...s}},"identity.getMutes":{dir:"out",fields:{...s}},"identity.getBlocked":{dir:"out",fields:{...s}},"identity.getBadges":{dir:"out",fields:{...s}},"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:{...s,key:"string"}},"storage.set":{dir:"out",fields:{...s,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...s,key:"string"}},"storage.keys":{dir:"out",fields:{...s}},"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:{...s,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...s,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...s}},"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:{...s}},"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:{...s,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:{...s,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:{...s,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:{...s}},"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:{...s,schema:"object"}},"config.get":{dir:"out",fields:{...s}},"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:{...s,url:"string"}},"resource.bytesMany":{dir:"out",fields:{...s,urls:"array"}},"resource.cancel":{dir:"out",fields:{...s}},"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:{...s}},"cvm.request":{dir:"out",fields:{...s,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...s,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:{...s,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...s,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...s,subId:"string"}},"outbox.publish":{dir:"out",fields:{...s,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...s,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:{...s,request:"object"}},"upload.status":{dir:"out",fields:{...s,uploadId:"string"}},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...s,request:"object"}},"intent.available":{dir:"out",fields:{...s,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...s}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"},"ble.open":{dir:"out",fields:{...s,request:"object"}},"ble.services":{dir:"out",fields:{...s,sessionId:"string"}},"ble.read":{dir:"out",fields:{...s,sessionId:"string",target:"object"}},"ble.write":{dir:"out",fields:{...s,sessionId:"string",target:"object",data:"array"}},"ble.subscribe":{dir:"out",fields:{...s,sessionId:"string",target:"object"}},"ble.unsubscribe":{dir:"out",fields:{...s,sessionId:"string",target:"object"}},"ble.close":{dir:"out",fields:{...s,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:{...s,input:"object"}},"common.decodeNip19":{dir:"out",fields:{...s,value:"string"}},"common.getProfile":{dir:"out",fields:{...s,target:"string"}},"common.follows":{dir:"out",fields:{...s}},"common.follow":{dir:"out",fields:{...s,pubkeys:"array"}},"common.unfollow":{dir:"out",fields:{...s,pubkeys:"array"}},"common.react":{dir:"out",fields:{...s,targetEventId:"string",reaction:"string"}},"common.report":{dir:"out",fields:{...s,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:{...s,request:"object"}},"webrtc.send":{dir:"out",fields:{...s,sessionId:"string",payload:"present"}},"webrtc.close":{dir:"out",fields:{...s,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:{...s,url:"string"}},"link.open.result":{dir:"in"},"lists.supported":{dir:"out",fields:{...s}},"lists.add":{dir:"out",fields:{...s,list:"object",items:"array"}},"lists.remove":{dir:"out",fields:{...s,list:"object",items:"array"}},"lists.supported.result":{dir:"in"},"lists.add.result":{dir:"in"},"lists.remove.result":{dir:"in"},"serial.open":{dir:"out",fields:{...s,request:"object"}},"serial.write":{dir:"out",fields:{...s,sessionId:"string",data:"array"}},"serial.close":{dir:"out",fields:{...s,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:{...s}},"dm.conversations":{dir:"out",fields:{...s}},"dm.messages":{dir:"out",fields:{...s,conversationId:"string"}},"dm.send":{dir:"out",fields:{...s,recipients:"array",content:"string"}},"dm.subscribe":{dir:"out",fields:{...s}},"dm.unsubscribe":{dir:"out",fields:{...s,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 L(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 G(e,t){return t==="present"?e!=null:L(e)===t}function K(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,d=r.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 n=d.slice(0,i);if(!P.includes(n))return{ok:!1,type:d,domain:n,errors:[{code:"unknown-domain",message:`"${n}" is not a known NAP domain`}]};const m=F[d];if(!m)return{ok:!1,type:d,domain:n,errors:[{code:"unknown-type",message:`"${d}" is not a known ${n} message type`}]};if(m.dir==="in")return{ok:!1,type:d,domain:n,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${d}" is a shell→napplet message; a napplet must not emit it`}]};for(const[l,y]of Object.entries(m.fields??{})){if(!(l in r)||r[l]===void 0){t.push({code:"missing-field",message:`Required field "${l}" is missing`,field:l});continue}G(r[l],y)||t.push({code:"wrong-type",message:`Field "${l}" should be ${y} but is ${L(r[l])}`,field:l})}return{ok:t.length===0,type:d,domain:n,direction:"out",errors:t}}var R=/^[a-z0-9][a-z0-9._:-]*$/;function U(e){return e.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g,(t,r)=>{if(r[0]==="#"){const i=r[1]==="x"||r[1]==="X"?parseInt(r.slice(2),16):parseInt(r.slice(1),10);return Number.isFinite(i)?String.fromCodePoint(i):t}return{amp:"&",lt:"<",gt:">",quot:'"',apos:"'"}[r]??t})}function S(e,t){const r=/<meta\b[^>]*>/gi;let d;for(;(d=r.exec(e))!==null;){const i=d[0],n=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);if((n?.[1]??n?.[2]??"").trim()!==t)continue;const m=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);return U((m?.[1]??m?.[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,t={}){const r=[],d=[],i=S(e,"napplet-type");i?R.test(i)||r.push({code:"invalid-napplet-type",message:`napplet-type "${i}" must match ${R}`}):r.push({code:"missing-napplet-type",message:'No <meta name="napplet-type"> declared'});const n=S(e,"napplet-requires"),a=n?n.split(",").map(l=>l.trim()).filter(Boolean):[];for(const l of a){const y=l.startsWith("nap:")?l.slice(4):l;P.includes(y)||r.push({code:"unknown-required-nap",message:`napplet-requires lists "${l}" which is not a known NAP domain`})}const m=S(e,"napplet-config-schema");if(m){let l;try{l=JSON.parse(m)}catch{l=void 0,r.push({code:"invalid-config-schema",message:"napplet-config-schema is not valid JSON"})}l!==void 0&&(typeof l!="object"||l===null||Array.isArray(l)?r.push({code:"invalid-config-schema",message:"napplet-config-schema must be a JSON Schema object"}):q(l)&&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:i,requires:a,errors:r,warnings:d}}var w="f".repeat(64),J="https://reference.invalid/blob",o=e=>[e],c=()=>[],W={"relay.subscribe":e=>o({type:"relay.eose",subId:e.subId}),"relay.close":c,"relay.publish":e=>o({type:"relay.publish.result",id:e.id,ok:!0,event:e.event}),"relay.publishEncrypted":e=>o({type:"relay.publishEncrypted.result",id:e.id,ok:!0,event:e.event}),"relay.query":e=>o({type:"relay.query.result",id:e.id,events:[]}),"identity.getPublicKey":e=>o({type:"identity.getPublicKey.result",id:e.id,pubkey:w}),"identity.getRelays":e=>o({type:"identity.getRelays.result",id:e.id,relays:{}}),"identity.getProfile":e=>o({type:"identity.getProfile.result",id:e.id,profile:null}),"identity.getFollows":e=>o({type:"identity.getFollows.result",id:e.id,pubkeys:[]}),"identity.getList":e=>o({type:"identity.getList.result",id:e.id,entries:[]}),"identity.getZaps":e=>o({type:"identity.getZaps.result",id:e.id,zaps:[]}),"identity.getMutes":e=>o({type:"identity.getMutes.result",id:e.id,pubkeys:[]}),"identity.getBlocked":e=>o({type:"identity.getBlocked.result",id:e.id,pubkeys:[]}),"identity.getBadges":e=>o({type:"identity.getBadges.result",id:e.id,badges:[]}),"storage.get":e=>o({type:"storage.get.result",id:e.id,value:null}),"storage.set":e=>o({type:"storage.set.result",id:e.id}),"storage.remove":e=>o({type:"storage.remove.result",id:e.id}),"storage.keys":e=>o({type:"storage.keys.result",id:e.id,keys:[]}),"inc.emit":c,"inc.subscribe":e=>o({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":c,"inc.channel.open":e=>o({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=>o({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":c,"theme.get":e=>o({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":c,"keys.registerAction":e=>o({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":c,"media.session.create":e=>o({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=>o({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=>o({type:"notify.permission.result",id:e.id,granted:!0}),"config.registerSchema":e=>o({type:"config.registerSchema.result",id:e.id,ok:!0}),"config.get":e=>o({type:"config.values",id:e.id,values:{}}),"config.subscribe":()=>o({type:"config.values",values:{}}),"config.unsubscribe":c,"config.openSettings":c,"resource.bytes":e=>o({type:"resource.bytes.result",id:e.id,blob:new Blob([]),mime:"application/octet-stream"}),"resource.bytesMany":e=>o({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=>o({type:"cvm.discover.result",id:e.id,servers:[]}),"cvm.request":e=>o({type:"cvm.request.result",id:e.id,message:{}}),"cvm.close":e=>o({type:"cvm.close.result",id:e.id}),"outbox.query":e=>o({type:"outbox.query.result",id:e.id,events:[],relays:{}}),"outbox.subscribe":e=>o({type:"outbox.eose",subId:e.subId}),"outbox.close":c,"outbox.publish":e=>o({type:"outbox.publish.result",id:e.id,ok:!0}),"outbox.resolveRelays":e=>o({type:"outbox.resolveRelays.result",id:e.id,plan:{}}),"upload.upload":e=>o({type:"upload.upload.result",id:e.id,result:{url:J}}),"upload.status":e=>o({type:"upload.status.result",id:e.id,status:{}}),"intent.invoke":e=>o({type:"intent.invoke.result",id:e.id,result:{ok:!0,archetype:"reference",action:"open",handled:!0}}),"intent.available":e=>o({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=>o({type:"intent.handlers.result",id:e.id,handlers:[]}),"ble.open":e=>o({type:"ble.open.result",id:e.id,session:{id:"ble-reference",state:"open",device:{id:"reference-device",name:"Reference BLE"}}}),"ble.services":e=>o({type:"ble.services.result",id:e.id,services:[]}),"ble.read":e=>o({type:"ble.read.result",id:e.id,data:[]}),"ble.write":e=>o({type:"ble.write.result",id:e.id}),"ble.subscribe":e=>o({type:"ble.subscribe.result",id:e.id}),"ble.unsubscribe":e=>o({type:"ble.unsubscribe.result",id:e.id}),"ble.close":e=>o({type:"ble.close.result",id:e.id}),"common.encodeNip19":e=>o({type:"common.encodeNip19.result",id:e.id,ok:!0,value:"npub1reference",nip19Type:"npub"}),"common.decodeNip19":e=>o({type:"common.decodeNip19.result",id:e.id,ok:!0,nip19Type:"npub",hex:w}),"common.getProfile":e=>o({type:"common.getProfile.result",id:e.id,ok:!0,pubkey:w,profile:null}),"common.follows":e=>o({type:"common.follows.result",id:e.id,ok:!0,pubkeys:[]}),"common.follow":e=>o({type:"common.follow.result",id:e.id,ok:!0}),"common.unfollow":e=>o({type:"common.unfollow.result",id:e.id,ok:!0}),"common.react":e=>o({type:"common.react.result",id:e.id,ok:!0,eventId:"0".repeat(64)}),"common.report":e=>o({type:"common.report.result",id:e.id,ok:!0,eventId:"1".repeat(64)}),"webrtc.open":e=>o({type:"webrtc.open.result",id:e.id,session:{id:"webrtc-reference",scope:{type:"direct",pubkey:w},channel:"default",state:"connecting"}}),"webrtc.send":e=>o({type:"webrtc.send.result",id:e.id}),"webrtc.close":e=>o({type:"webrtc.close.result",id:e.id}),"link.open":e=>o({type:"link.open.result",id:e.id,status:"opened"}),"lists.supported":e=>o({type:"lists.supported.result",id:e.id,lists:[]}),"lists.add":e=>o({type:"lists.add.result",id:e.id,ok:!0,added:0,skipped:0}),"lists.remove":e=>o({type:"lists.remove.result",id:e.id,ok:!0,removed:0,skipped:0}),"serial.open":e=>o({type:"serial.open.result",id:e.id,session:{id:`serial-${String(e.id)}`,state:"open"}}),"serial.write":e=>o({type:"serial.write.result",id:e.id}),"serial.close":e=>o({type:"serial.close.result",id:e.id})};function Z(e={}){const t=e.now??(()=>Date.now()),r=[];function d(i){const n=i&&typeof i=="object"&&typeof i.type=="string"?i.type:void 0;if(r.push({envelope:i,verdict:K(i),timestamp:t()}),!n)return[];const a=W[n];return a?a(i):[]}return{get records(){return r},handle:d,reset(){r.length=0}}}function z(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}}var M="__nappletConformance.error";function O(e){return new Promise(t=>setTimeout(t,e))}function V(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function Y(e){return`
2
- (() => {
3
- const domains = ${JSON.stringify(e)};
4
- const napplet = {};
5
- for (const domain of domains) napplet[domain] = {};
6
- window.napplet = napplet;
7
- const report = (reason) => {
8
- try {
9
- window.parent.postMessage({ type: '${M}', reason: String(reason && reason.message || reason) }, '*');
10
- } catch {}
11
- };
12
- window.addEventListener('error', (event) => report(event.error || event.message));
13
- window.addEventListener('unhandledrejection', (event) => report(event.reason));
14
- })();
15
- `}function X(e,t,r){const d=`<base href="${V(t)}">`,i=`<script>${Y(r)}<\/script>`,n=/<head(?:\s[^>]*)?>/i.exec(e);if(n){const a=n.index+n[0].length;return`${e.slice(0,a)}${d}${i}${e.slice(a)}`}return`${d}${i}${e}`}function Q(e){if(!e.startsWith("data:"))return null;const t=e.indexOf(",");if(t<0)return"";const r=e.slice(0,t),d=e.slice(t+1);return r.endsWith(";base64")?atob(d):decodeURIComponent(d)}async function ee(e){if(e==="about:blank")return"<!doctype html><html><head></head><body></body></html>";const t=Q(e);if(t!==null)return t;const r=await fetch(e);if(!r.ok)throw new Error(`Failed to fetch napplet HTML: ${r.status} ${r.statusText}`);return r.text()}async function x(e,t,r,d){const i=Z(),n=r.createElement("iframe");n.setAttribute("sandbox",t.allowSameOrigin?"allow-scripts allow-same-origin":"allow-scripts"),n.setAttribute("aria-hidden","true"),n.style.position="absolute",n.style.width="1px",n.style.height="1px",n.style.left="-9999px";let a=null;const m=p=>{if(p.source!==n.contentWindow)return;const h=p.data;if(h&&typeof h=="object"&&h.type===M){a=`napplet boot error: ${String(h.reason??"unknown error")}`;return}const D=i.handle(p.data),H=p.source;for(const _ of D)H?.postMessage(_,"*")};let l=!1;const y=new Promise(p=>{n.addEventListener("load",()=>{l=!0,p()},{once:!0})});d.addEventListener("message",m),(r.body??r.documentElement).appendChild(n);try{const p=await ee(t.url);n.srcdoc=X(p,t.url,e)}catch(p){a=p instanceof Error?p.message:String(p)}if(!a){const p=Symbol("timeout");await Promise.race([y.then(()=>"loaded"),O(t.readyTimeoutMs).then(()=>p)])===p||!l?a=`napplet did not boot (iframe did not load within ${t.readyTimeoutMs}ms; a conformant napplet must run under sandbox="allow-scripts" without allow-same-origin)`:await O(t.settleMs)}d.removeEventListener("message",m);try{n.remove()}catch{}return{installedGlobal:a===null,bootError:a,emitted:[...i.records]}}async function te(e){const t=e.document??document,r=e.window??window,d={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},i=await x(P,d,t,r);let n=null;{const a=await x([],d,t,r);n={bootError:a.bootError,emitted:a.emitted}}return{installedGlobal:i.installedGlobal,bootError:i.bootError,emitted:i.emitted,degraded:n}}var ie=e=>({status:"pass",detail:e}),re=(e,t)=>({status:"fail",detail:e,diagnostics:t}),se=e=>({status:"skip",detail:e}),u={pass:ie,fail:re,skip:se};function ne(e,t){return $(e).errors.filter(r=>t.includes(r.code))}function B(e,t){return new RegExp(`name\\s*=\\s*["']${t}["']`,"i").test(e)}function I(e,t,r,d){return{id:e,area:"manifest",severity:"error",title:t,run:i=>{if(d&&!B(i.manifestHtml,d))return u.skip(`No ${d} declared`);const n=ne(i.manifestHtml,r);return n.length?u.fail(n[0].message,n):u.pass()}}}var oe=[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:"Runs after runtime-injected window.napplet is installed",run:e=>e.installedGlobal?u.pass():u.fail("runtime did not inject window.napplet before load")},{id:"boot/no-boot-error",area:"boot",severity:"error",title:"Boots without an uncaught error",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 r=t.map(d=>({envelope:d.envelope,errors:d.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(!B(e.manifestHtml,"napplet-requires"))return u.skip("No napplet-requires to compare against");const t=new Set($(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/domain-absence",area:"degradation",severity:"error",title:"Does not crash when no NAP domains are injected",run:e=>e.degraded?e.degraded.bootError?u.fail(`Crashed with no injected domains: ${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 de(e,t={}){const r=t.now??(()=>Date.now()),d=t.checks??oe,i=r(),n=d.map(l=>{const y=l.run(e);return{id:l.id,area:l.area,severity:l.severity,title:l.title,status:y.status,detail:y.detail,diagnostics:y.diagnostics}}),a={total:n.length,passed:n.filter(l=>l.status==="pass").length,failed:n.filter(l=>l.status==="fail").length,skipped:n.filter(l=>l.status==="skip").length,errors:n.filter(l=>l.status==="fail"&&l.severity==="error").length,warnings:n.filter(l=>l.status==="fail"&&l.severity==="warning").length};return{napplet:$(e.manifestHtml).nappletType,startedAt:i,finishedAt:r(),checks:n,summary:a,ok:a.errors===0}}const le=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,ae="/__conformance__/events",C=new URLSearchParams(location.search),T=C.get("live")==="1";function b(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const g=document.getElementById("app");function ue(){return g.innerHTML=`
16
- <header class="masthead">
17
- <h1>napplet conformance ${T?'<span class="live-badge" id="live-badge">● live</span>':""}</h1>
18
- <p>Load a napplet by URL and run NAP protocol conformance live. Same engine as <code>napplet-conformance</code> CI.</p>
19
- </header>
20
- <form id="run-form" class="runbar">
21
- <input id="url" type="url" placeholder="https://localhost:5173/ (a napplet served with permissive CORS)" autocomplete="off" />
22
- <button id="run" type="submit">Run conformance</button>
23
- <button id="rerun" type="button" class="secondary" hidden>Re-run</button>
24
- </form>
25
- <p id="status" class="status" role="status"></p>
26
- <section id="output" class="output"></section>
27
- `,{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=ue();let v="",A=0;function k(e,t="info"){f.status.textContent=e,f.status.dataset.kind=t}function N(e){return String(e).padStart(2,"0")}function ce(e){const t=new Date(e);return`${N(t.getHours())}:${N(t.getMinutes())}:${N(t.getSeconds())}`}async function pe(e){try{const r=await(await fetch(e,{mode:"cors"})).text();return{html:r,forbidden:le.test(r)?["window.nostr"]:[],fetched:!0}}catch{return{html:"",forbidden:[],fetched:!1}}}function fe(e){const t=e.ok?"conformant":"non-conformant",d=[...new Set(e.checks.map(a=>a.area))].map(a=>{const m=e.checks.filter(l=>l.area===a).map(l=>{const y=l.severity==="warning"&&l.status==="fail"?' <span class="tag warn">warning</span>':"",p=l.detail?`<span class="detail">${b(l.detail)}</span>`:"";return`<li class="check ${l.status}"><span class="badge ${l.status}">${l.status}</span><span class="id">${b(l.id)}</span>${y}${p}</li>`}).join("");return`<div class="area"><h3>${b(a)}</h3><ul>${m}</ul></div>`}).join(""),i=e.summary,n=A>1?`<span class="run-stamp">run #${A} · ${ce(e.finishedAt)}</span>`:"";return`
28
- <div class="verdict ${t}">
29
- <span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
30
- <span class="counts">${i.passed} passed · ${i.failed} failed · ${i.skipped} skipped${i.warnings?` · ${i.warnings} warning(s)`:""}</span>
31
- ${n}
32
- </div>
33
- <div class="checks">${d}</div>
34
- `}function me(e){return e.length===0?'<p class="empty">No envelopes were emitted.</p>':`<ul class="envlog">${e.map(r=>{const d=r.verdict.ok?"ok":"bad",i=r.verdict.ok?"":`<div class="errs">${r.verdict.errors.map(n=>b(`${n.code}: ${n.message}`)).join("<br>")}</div>`;return`<li class="env ${d}"><code>${b(JSON.stringify(r.envelope))}</code>${i}</li>`}).join("")}</ul>`}function ye(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),d=(i,n)=>`<tr><th>${b(i)}</th><td>${n?b(String(n)):"<em>—</em>"}</td></tr>`;return`
35
- <table class="manifest">
36
- ${d("napplet-type",r.nappletType)}
37
- ${d("requires",r.requires.join(", "))}
38
- </table>
39
- ${r.errors.length?`<div class="errs">${r.errors.map(i=>b(`${i.code}: ${i.message}`)).join("<br>")}</div>`:""}
40
- `}function be(e,t,r,d){f.output.innerHTML=`
41
- ${fe(e)}
42
- <div class="panels">
43
- <section class="panel"><h2>Emitted envelopes (${t.length})</h2>${me(t)}</section>
44
- <section class="panel"><h2>Manifest</h2>${ye(r,d)}</section>
45
- </div>
46
- `}async function E(e){v=e,f.runButton.disabled=!0,f.rerunButton.disabled=!0;try{k(`Fetching manifest for ${e} …`);const{html:t,forbidden:r,fetched:d}=await pe(e);k("Booting napplet in a sandboxed iframe …");const i=await te({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),n=z({manifestHtml:t,boot:i,forbiddenGlobals:r}),a=de(n);A+=1,k(a.ok?"Done — conformant.":"Done — non-conformant.",a.ok?"info":"error"),be(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 ge(){if(!T||typeof EventSource>"u")return;let e=!1;const t=new EventSource(ae);t.addEventListener("rerun",()=>{!v||e||(e=!0,setTimeout(()=>{e=!1,E(v)},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),E(t)}});f.rerunButton.addEventListener("click",()=>{v&&E(v)});ge();const j=C.get("url");j&&(f.urlInput.value=j,E(j));