@napplet/conformance-cli 0.2.11 → 0.2.13

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", "dm"];
4
+ var NAP_DOMAINS = ["relay", "identity", "storage", "inc", "theme", "keys", "media", "notify", "config", "resource", "cvm", "outbox", "upload", "intent", "ble", "webrtc", "link", "count", "lists", "serial", "common", "dm"];
5
5
  function createDispatch() {
6
6
  const handlers = /* @__PURE__ */ new Map();
7
7
  function registerNap2(domain, handler) {
@@ -161,7 +161,6 @@ var NappletConformanceHostBundle = (() => {
161
161
  "outbox.getEvent.result": { dir: "in" },
162
162
  "outbox.query.result": { dir: "in" },
163
163
  "outbox.event": { dir: "in" },
164
- "outbox.eose": { dir: "in" },
165
164
  "outbox.closed": { dir: "in" },
166
165
  "outbox.publish.result": { dir: "in" },
167
166
  "outbox.resolveRelays.result": { dir: "in" },
@@ -225,6 +224,9 @@ var NappletConformanceHostBundle = (() => {
225
224
  // ── link ─────────────────────────────────────────────────────────────────
226
225
  "link.open": { dir: "out", fields: { ...ID, url: "string" } },
227
226
  "link.open.result": { dir: "in" },
227
+ // ── count ────────────────────────────────────────────────────────────────
228
+ "count.query": { dir: "out", fields: { ...ID, filters: "array" } },
229
+ "count.query.result": { dir: "in" },
228
230
  // ── lists ────────────────────────────────────────────────────────────────
229
231
  "lists.supported": { dir: "out", fields: { ...ID } },
230
232
  "lists.add": { dir: "out", fields: { ...ID, list: "object", items: "array" } },
@@ -477,9 +479,9 @@ var NappletConformanceHostBundle = (() => {
477
479
  "cvm.request": (e) => ok({ type: "cvm.request.result", id: e.id, message: {} }),
478
480
  "cvm.close": (e) => ok({ type: "cvm.close.result", id: e.id }),
479
481
  // outbox
480
- "outbox.getEvent": (e) => ok({ type: "outbox.getEvent.result", id: e.id, relays: [] }),
481
- "outbox.query": (e) => ok({ type: "outbox.query.result", id: e.id, events: [], relays: {} }),
482
- "outbox.subscribe": (e) => ok({ type: "outbox.eose", subId: e.subId }),
482
+ "outbox.getEvent": (e) => ok({ type: "outbox.getEvent.result", id: e.id }),
483
+ "outbox.query": (e) => ok({ type: "outbox.query.result", id: e.id, events: [] }),
484
+ "outbox.subscribe": (e) => ok({ type: "outbox.closed", subId: e.subId, reason: "reference shell complete" }),
483
485
  "outbox.close": none,
484
486
  "outbox.publish": (e) => ok({ type: "outbox.publish.result", id: e.id, ok: true }),
485
487
  "outbox.resolveRelays": (e) => ok({ type: "outbox.resolveRelays.result", id: e.id, plan: {} }),
@@ -557,6 +559,8 @@ var NappletConformanceHostBundle = (() => {
557
559
  "webrtc.close": (e) => ok({ type: "webrtc.close.result", id: e.id }),
558
560
  // link
559
561
  "link.open": (e) => ok({ type: "link.open.result", id: e.id, status: "opened" }),
562
+ // count
563
+ "count.query": (e) => ok({ type: "count.query.result", id: e.id, ok: true, count: 0 }),
560
564
  // lists
561
565
  "lists.supported": (e) => ok({ type: "lists.supported.result", id: e.id, lists: [] }),
562
566
  "lists.add": (e) => ok({ type: "lists.add.result", id: e.id, ok: true, added: 0, skipped: 0 }),
@@ -0,0 +1,46 @@
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))d(r);new MutationObserver(r=>{for(const o of r)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(r){const o={};return r.integrity&&(o.integrity=r.integrity),r.referrerPolicy&&(o.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?o.credentials="include":r.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function d(r){if(r.ep)return;r.ep=!0;const o=s(r);fetch(r.href,o)}})();var P=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent","ble","webrtc","link","count","lists","serial","common","dm"],i={id:"string"},F={"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.info":{dir:"out",fields:{...i}},"resource.bytes":{dir:"out",fields:{...i,url:"string"}},"resource.bytesMany":{dir:"out",fields:{...i,urls:"array"}},"resource.cancel":{dir:"out",fields:{...i}},"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:{...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.getEvent":{dir:"out",fields:{...i,eventId:"string"}},"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.getEvent.result":{dir:"in"},"outbox.query.result":{dir:"in"},"outbox.event":{dir:"in"},"outbox.closed":{dir:"in"},"outbox.publish.result":{dir:"in"},"outbox.resolveRelays.result":{dir:"in"},"upload.info":{dir:"out",fields:{...i}},"upload.upload":{dir:"out",fields:{...i,request:"object"}},"upload.status":{dir:"out",fields:{...i,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:{...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"},"count.query":{dir:"out",fields:{...i,filters:"array"}},"count.query.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 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 r=d.indexOf(".");if(r<=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,r);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 R=/^[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 r=s[1]==="x"||s[1]==="X"?parseInt(s.slice(2),16):parseInt(s.slice(1),10);return Number.isFinite(r)?String.fromCodePoint(r):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 r=d[0],o=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(r);if((o?.[1]??o?.[2]??"").trim()!==t)continue;const m=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(r);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=[],r=S(e,"napplet-type");r?R.test(r)||s.push({code:"invalid-napplet-type",message:`napplet-type "${r}" must match ${R}`}):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:r,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}),"outbox.query":e=>n({type:"outbox.query.result",id:e.id,events:[]}),"outbox.subscribe":e=>n({type:"outbox.closed",subId:e.subId,reason:"reference shell complete"}),"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"}),"count.query":e=>n({type:"count.query.result",id:e.id,ok:!0,count:0}),"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(r){const o=r&&typeof r=="object"&&typeof r.type=="string"?r.type:void 0;if(s.push({envelope:r,verdict:K(r),timestamp:t()}),!o)return[];const a=W[o];return a?a(r):[]}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 x(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)}">`,r=`<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}${r}${e.slice(a)}`}return`${d}${r}${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 r=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=r.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"),x(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 x(t.settleMs)}d.removeEventListener("message",m);try{o.remove()}catch{}return{installedGlobal:a===null,bootError:a,emitted:[...r.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},r=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:r.installedGlobal,bootError:r.bootError,emitted:r.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:r=>{if(d&&!B(r.manifestHtml,d))return u.skip(`No ${d} declared`);const o=ne(r.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(r=>r.startsWith("nap:")?r.slice(4):r)),d=[...new Set(e.emitted.map(r=>r.verdict.domain).filter(r=>typeof r=="string"))].filter(r=>!t.has(r));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,r=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:r,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(""),r=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">${r.passed} passed · ${r.failed} failed · ${r.skipped} skipped${r.warnings?` · ${r.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",r=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>${r}</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=(r,o)=>`<tr><th>${b(r)}</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(r=>b(`${r.code}: ${r.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 r=await te({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),o=z({manifestHtml:t,boot:r,forbiddenGlobals:s}),a=de(o);A+=1,k(a.ok?"Done — conformant.":"Done — non-conformant.",a.ok?"info":"error"),be(a,r.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-y56oSaYx.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-DntSpXgj.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.11",
3
+ "version": "0.2.13",
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,14 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "playwright": "^1.59.1",
18
- "@napplet/conformance": "^0.10.0"
18
+ "@napplet/conformance": "^0.12.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.11"
24
+ "vitest": "^4.1.2",
25
+ "@napplet/conformance-web": "0.0.13"
25
26
  },
26
27
  "license": "MIT",
27
28
  "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 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));