@napplet/conformance-cli 0.2.5 → 0.2.6
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", "webrtc", "link", "serial"];
|
|
4
|
+
var NAP_DOMAINS = ["relay", "identity", "storage", "inc", "theme", "keys", "media", "notify", "config", "resource", "cvm", "outbox", "upload", "intent", "webrtc", "link", "lists", "serial", "common"];
|
|
5
5
|
function createDispatch() {
|
|
6
6
|
const handlers = /* @__PURE__ */ new Map();
|
|
7
7
|
function registerNap2(domain, handler) {
|
|
@@ -177,6 +177,23 @@ var NappletConformanceHostBundle = (() => {
|
|
|
177
177
|
"intent.available.result": { dir: "in" },
|
|
178
178
|
"intent.handlers.result": { dir: "in" },
|
|
179
179
|
"intent.changed": { dir: "in" },
|
|
180
|
+
// ── common ───────────────────────────────────────────────────────────────
|
|
181
|
+
"common.encodeNip19": { dir: "out", fields: { ...ID, input: "object" } },
|
|
182
|
+
"common.decodeNip19": { dir: "out", fields: { ...ID, value: "string" } },
|
|
183
|
+
"common.getProfile": { dir: "out", fields: { ...ID, target: "string" } },
|
|
184
|
+
"common.follows": { dir: "out", fields: { ...ID } },
|
|
185
|
+
"common.follow": { dir: "out", fields: { ...ID, pubkeys: "array" } },
|
|
186
|
+
"common.unfollow": { dir: "out", fields: { ...ID, pubkeys: "array" } },
|
|
187
|
+
"common.react": { dir: "out", fields: { ...ID, targetEventId: "string", reaction: "string" } },
|
|
188
|
+
"common.report": { dir: "out", fields: { ...ID, target: "object", reason: "string", text: "string" } },
|
|
189
|
+
"common.encodeNip19.result": { dir: "in" },
|
|
190
|
+
"common.decodeNip19.result": { dir: "in" },
|
|
191
|
+
"common.getProfile.result": { dir: "in" },
|
|
192
|
+
"common.follows.result": { dir: "in" },
|
|
193
|
+
"common.follow.result": { dir: "in" },
|
|
194
|
+
"common.unfollow.result": { dir: "in" },
|
|
195
|
+
"common.react.result": { dir: "in" },
|
|
196
|
+
"common.report.result": { dir: "in" },
|
|
180
197
|
// ── webrtc ────────────────────────────────────────────────────────────────
|
|
181
198
|
"webrtc.open": { dir: "out", fields: { ...ID, request: "object" } },
|
|
182
199
|
"webrtc.send": { dir: "out", fields: { ...ID, sessionId: "string", payload: "present" } },
|
|
@@ -188,6 +205,13 @@ var NappletConformanceHostBundle = (() => {
|
|
|
188
205
|
// ── link ─────────────────────────────────────────────────────────────────
|
|
189
206
|
"link.open": { dir: "out", fields: { ...ID, url: "string" } },
|
|
190
207
|
"link.open.result": { dir: "in" },
|
|
208
|
+
// ── lists ────────────────────────────────────────────────────────────────
|
|
209
|
+
"lists.supported": { dir: "out", fields: { ...ID } },
|
|
210
|
+
"lists.add": { dir: "out", fields: { ...ID, list: "object", items: "array" } },
|
|
211
|
+
"lists.remove": { dir: "out", fields: { ...ID, list: "object", items: "array" } },
|
|
212
|
+
"lists.supported.result": { dir: "in" },
|
|
213
|
+
"lists.add.result": { dir: "in" },
|
|
214
|
+
"lists.remove.result": { dir: "in" },
|
|
191
215
|
// ── serial ───────────────────────────────────────────────────────────────
|
|
192
216
|
"serial.open": { dir: "out", fields: { ...ID, request: "object" } },
|
|
193
217
|
"serial.write": { dir: "out", fields: { ...ID, sessionId: "string", data: "array" } },
|
|
@@ -428,6 +452,15 @@ var NappletConformanceHostBundle = (() => {
|
|
|
428
452
|
}
|
|
429
453
|
}),
|
|
430
454
|
"intent.handlers": (e) => ok({ type: "intent.handlers.result", id: e.id, handlers: [] }),
|
|
455
|
+
// common
|
|
456
|
+
"common.encodeNip19": (e) => ok({ type: "common.encodeNip19.result", id: e.id, ok: true, value: "npub1reference", nip19Type: "npub" }),
|
|
457
|
+
"common.decodeNip19": (e) => ok({ type: "common.decodeNip19.result", id: e.id, ok: true, nip19Type: "npub", hex: REFERENCE_PUBKEY }),
|
|
458
|
+
"common.getProfile": (e) => ok({ type: "common.getProfile.result", id: e.id, ok: true, pubkey: REFERENCE_PUBKEY, profile: null }),
|
|
459
|
+
"common.follows": (e) => ok({ type: "common.follows.result", id: e.id, ok: true, pubkeys: [] }),
|
|
460
|
+
"common.follow": (e) => ok({ type: "common.follow.result", id: e.id, ok: true }),
|
|
461
|
+
"common.unfollow": (e) => ok({ type: "common.unfollow.result", id: e.id, ok: true }),
|
|
462
|
+
"common.react": (e) => ok({ type: "common.react.result", id: e.id, ok: true, eventId: "0".repeat(64) }),
|
|
463
|
+
"common.report": (e) => ok({ type: "common.report.result", id: e.id, ok: true, eventId: "1".repeat(64) }),
|
|
431
464
|
// webrtc
|
|
432
465
|
"webrtc.open": (e) => ok({
|
|
433
466
|
type: "webrtc.open.result",
|
|
@@ -443,6 +476,10 @@ var NappletConformanceHostBundle = (() => {
|
|
|
443
476
|
"webrtc.close": (e) => ok({ type: "webrtc.close.result", id: e.id }),
|
|
444
477
|
// link
|
|
445
478
|
"link.open": (e) => ok({ type: "link.open.result", id: e.id, status: "opened" }),
|
|
479
|
+
// lists
|
|
480
|
+
"lists.supported": (e) => ok({ type: "lists.supported.result", id: e.id, lists: [] }),
|
|
481
|
+
"lists.add": (e) => ok({ type: "lists.add.result", id: e.id, ok: true, added: 0, skipped: 0 }),
|
|
482
|
+
"lists.remove": (e) => ok({ type: "lists.remove.result", id: e.id, ok: true, removed: 0, skipped: 0 }),
|
|
446
483
|
// serial
|
|
447
484
|
"serial.open": (e) => ok({ type: "serial.open.result", id: e.id, session: { id: `serial-${String(e.id)}`, state: "open" } }),
|
|
448
485
|
"serial.write": (e) => ok({ type: "serial.write.result", id: e.id }),
|
|
@@ -0,0 +1,32 @@
|
|
|
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"]'))a(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const d of s.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&a(d)}).observe(document,{childList:!0,subtree:!0});function r(i){const s={};return i.integrity&&(s.integrity=i.integrity),i.referrerPolicy&&(s.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?s.credentials="include":i.crossOrigin==="anonymous"?s.credentials="omit":s.credentials="same-origin",s}function a(i){if(i.ep)return;i.ep=!0;const s=r(i);fetch(i.href,s)}})();var j=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent","webrtc","link","lists","serial","common"],G=["shell"],n={id:"string"},K={"shell.ready":{dir:"out"},"shell.init":{dir:"in"},"relay.subscribe":{dir:"out",fields:{...n,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...n,subId:"string"}},"relay.publish":{dir:"out",fields:{...n,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...n,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...n,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:{...n}},"identity.getRelays":{dir:"out",fields:{...n}},"identity.getProfile":{dir:"out",fields:{...n}},"identity.getFollows":{dir:"out",fields:{...n}},"identity.getList":{dir:"out",fields:{...n,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...n}},"identity.getMutes":{dir:"out",fields:{...n}},"identity.getBlocked":{dir:"out",fields:{...n}},"identity.getBadges":{dir:"out",fields:{...n}},"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:{...n,key:"string"}},"storage.set":{dir:"out",fields:{...n,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...n,key:"string"}},"storage.keys":{dir:"out",fields:{...n}},"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:{...n,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...n,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...n}},"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:{...n}},"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:{...n,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:{...n,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:{...n,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:{...n}},"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:{...n,schema:"object"}},"config.get":{dir:"out",fields:{...n}},"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:{...n,url:"string"}},"resource.cancel":{dir:"out",fields:{...n}},"resource.bytes.result":{dir:"in"},"resource.bytes.error":{dir:"in"},"cvm.discover":{dir:"out",fields:{...n}},"cvm.request":{dir:"out",fields:{...n,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...n,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:{...n,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...n,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...n,subId:"string"}},"outbox.publish":{dir:"out",fields:{...n,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...n,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:{...n,request:"object"}},"upload.status":{dir:"out",fields:{...n,uploadId:"string"}},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...n,request:"object"}},"intent.available":{dir:"out",fields:{...n,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...n}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"},"common.encodeNip19":{dir:"out",fields:{...n,input:"object"}},"common.decodeNip19":{dir:"out",fields:{...n,value:"string"}},"common.getProfile":{dir:"out",fields:{...n,target:"string"}},"common.follows":{dir:"out",fields:{...n}},"common.follow":{dir:"out",fields:{...n,pubkeys:"array"}},"common.unfollow":{dir:"out",fields:{...n,pubkeys:"array"}},"common.react":{dir:"out",fields:{...n,targetEventId:"string",reaction:"string"}},"common.report":{dir:"out",fields:{...n,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:{...n,request:"object"}},"webrtc.send":{dir:"out",fields:{...n,sessionId:"string",payload:"present"}},"webrtc.close":{dir:"out",fields:{...n,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:{...n,url:"string"}},"link.open.result":{dir:"in"},"lists.supported":{dir:"out",fields:{...n}},"lists.add":{dir:"out",fields:{...n,list:"object",items:"array"}},"lists.remove":{dir:"out",fields:{...n,list:"object",items:"array"}},"lists.supported.result":{dir:"in"},"lists.add.result":{dir:"in"},"lists.remove.result":{dir:"in"},"serial.open":{dir:"out",fields:{...n,request:"object"}},"serial.write":{dir:"out",fields:{...n,sessionId:"string",data:"array"}},"serial.close":{dir:"out",fields:{...n,sessionId:"string"}},"serial.open.result":{dir:"in"},"serial.write.result":{dir:"in"},"serial.close.result":{dir:"in"},"serial.event":{dir:"in"}};function M(e){if(e===void 0)return"undefined";if(e===null)return"null";if(Array.isArray(e))return"array";const t=typeof e;return t==="string"||t==="number"||t==="boolean"?t:t==="object"?"object":"present"}function U(e,t){return t==="present"?e!=null:M(e)===t}function Z(e){const t=[];if(typeof e!="object"||e===null||Array.isArray(e))return{ok:!1,errors:[{code:"not-an-object",message:"Envelope must be a non-null object"}]};const r=e,a=r.type;if(typeof a!="string")return{ok:!1,errors:[{code:"missing-type",message:"Envelope is missing a string `type` field"}]};const i=a.indexOf(".");if(i<=0)return{ok:!1,type:a,errors:[{code:"malformed-type",message:`Envelope type "${a}" is not in domain.action form`}]};const s=a.slice(0,i);if(!(j.includes(s)||G.includes(s)))return{ok:!1,type:a,domain:s,errors:[{code:"unknown-domain",message:`"${s}" is not a known NAP domain`}]};const p=K[a];if(!p)return{ok:!1,type:a,domain:s,errors:[{code:"unknown-type",message:`"${a}" is not a known ${s} message type`}]};if(p.dir==="in")return{ok:!1,type:a,domain:s,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${a}" is a shell→napplet message; a napplet must not emit it`}]};for(const[l,m]of Object.entries(p.fields??{})){if(!(l in r)||r[l]===void 0){t.push({code:"missing-field",message:`Required field "${l}" is missing`,field:l});continue}U(r[l],m)||t.push({code:"wrong-type",message:`Field "${l}" should be ${m} but is ${M(r[l])}`,field:l})}return{ok:t.length===0,type:a,domain:s,direction:"out",errors:t}}var R=/^[a-z0-9][a-z0-9._:-]*$/;function z(e){return e.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g,(t,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 N(e,t){const r=/<meta\b[^>]*>/gi;let a;for(;(a=r.exec(e))!==null;){const i=a[0],s=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);if((s?.[1]??s?.[2]??"").trim()!==t)continue;const p=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);return z((p?.[1]??p?.[2]??"").trim())}}function q(e){if(Array.isArray(e))return e.some(q);if(e&&typeof e=="object"){const t=e;return"pattern"in t?!0:Object.values(t).some(q)}return!1}function E(e,t={}){const r=[],a=[],i=N(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 s=N(e,"napplet-requires"),d=s?s.split(",").map(l=>l.trim()).filter(Boolean):[];for(const l of d){const m=l.startsWith("nap:")?l.slice(4):l;j.includes(m)||r.push({code:"unknown-required-nap",message:`napplet-requires lists "${l}" which is not a known NAP domain`})}const p=N(e,"napplet-config-schema");if(p){let l;try{l=JSON.parse(p)}catch{l=void 0,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:d,errors:r,warnings:a}}var w="f".repeat(64),J="https://reference.invalid/blob",o=e=>[e],c=()=>[],V={"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.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:[]}),"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 W(e={}){const t=e.now??(()=>Date.now()),r={domains:e.capabilities?.domains??[...j],protocols:e.capabilities?.protocols??{}},a=e.services??[],i=[];function s(d){const p=d&&typeof d=="object"&&typeof d.type=="string"?d.type:void 0;if(p==="shell.ready")return[{type:"shell.init",capabilities:{domains:r.domains,protocols:r.protocols},services:a}];if(i.push({envelope:d,verdict:Z(d),timestamp:t()}),!p)return[];const l=V[p];return l?l(d):[]}return{capabilities:r,services:a,get records(){return i},handle:s,reset(){i.length=0}}}function Y(e){return{manifestHtml:e.manifestHtml,sandbox:e.sandbox??{allowScripts:!0,allowSameOrigin:!1},installedGlobal:e.boot.installedGlobal,bootError:e.boot.bootError,emitted:e.boot.emitted,forbiddenGlobals:e.forbiddenGlobals??[],degraded:e.boot.degraded,lifecycle:e.lifecycle??null}}function x(e){return new Promise(t=>setTimeout(t,e))}async function B(e,t,r,a){const i=W(e?{capabilities:e}:void 0),s=r.createElement("iframe");s.setAttribute("sandbox",t.allowSameOrigin?"allow-scripts allow-same-origin":"allow-scripts"),s.setAttribute("aria-hidden","true"),s.style.position="absolute",s.style.width="1px",s.style.height="1px",s.style.left="-9999px";let d;const p=new Promise(b=>{d=b}),l=b=>{if(b.source!==s.contentWindow)return;const H=i.handle(b.data),F=b.source;for(const _ of H)F?.postMessage(_,"*");const S=b.data;S&&typeof S=="object"&&S.type==="shell.ready"&&d()};a.addEventListener("message",l),(r.body??r.documentElement).appendChild(s),s.setAttribute("src",t.url);let m=!1,v=null;const O=Symbol("timeout");await Promise.race([p.then(()=>"ready"),x(t.readyTimeoutMs).then(()=>O)])===O?v=`napplet did not boot (no shell.ready within ${t.readyTimeoutMs}ms; a conformant napplet must run under sandbox="allow-scripts" without allow-same-origin)`:(m=!0,await x(t.settleMs)),a.removeEventListener("message",l);try{s.remove()}catch{}return{installedGlobal:m,bootError:v,emitted:[...i.records]}}async function X(e){const t=e.document??document,r=e.window??window,a={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},i=await B(void 0,a,t,r);let s=null;{const d=await B({domains:[],protocols:{}},a,t,r);s={bootError:d.bootError,emitted:d.emitted}}return{installedGlobal:i.installedGlobal,bootError:i.bootError,emitted:i.emitted,degraded:s}}var Q=e=>({status:"pass",detail:e}),ee=(e,t)=>({status:"fail",detail:e,diagnostics:t}),te=e=>({status:"skip",detail:e}),u={pass:Q,fail:ee,skip:te};function ie(e,t){return E(e).errors.filter(r=>t.includes(r.code))}function C(e,t){return new RegExp(`name\\s*=\\s*["']${t}["']`,"i").test(e)}function A(e,t,r,a){return{id:e,area:"manifest",severity:"error",title:t,run:i=>{if(a&&!C(i.manifestHtml,a))return u.skip(`No ${a} declared`);const s=ie(i.manifestHtml,r);return s.length?u.fail(s[0].message,s):u.pass()}}}var re=[A("manifest/napplet-type","Declares a valid napplet-type",["missing-napplet-type","invalid-napplet-type"]),A("manifest/declared-naps","napplet-requires lists only real NAP domains",["unknown-required-nap"],"napplet-requires"),A("manifest/config-schema","Config schema is a draft-07 core subset",["invalid-config-schema"],"napplet-config-schema"),{id:"boot/sandbox-allow-scripts",area:"boot",severity:"error",title:"Runs under allow-scripts without allow-same-origin",run:e=>e.sandbox.allowScripts?e.sandbox.allowSameOrigin?u.fail("Napplet relied on allow-same-origin, which a shell must not grant"):u.pass():u.fail("Napplet was not loaded with allow-scripts")},{id:"boot/installs-global",area:"boot",severity:"error",title:"Completes the NAP-SHELL handshake (posts shell.ready)",run:e=>e.installedGlobal?u.pass():u.fail("window.napplet was not present after load (no NAP-SHELL shell.ready)")},{id:"boot/no-boot-error",area:"boot",severity:"error",title:"Boots without an uncaught error (observed via NAP-SHELL shell.ready)",run:e=>e.bootError?u.fail(e.bootError):u.pass()},{id:"boot/no-forbidden-globals",area:"boot",severity:"error",title:"Does not access forbidden globals (e.g. window.nostr)",run:e=>e.forbiddenGlobals.length?u.fail(`Accessed forbidden global(s): ${e.forbiddenGlobals.join(", ")}`,e.forbiddenGlobals):u.pass()},{id:"wire/envelope-well-formed",area:"wire",severity:"error",title:"Every emitted envelope is well-formed",run:e=>{if(e.emitted.length===0)return u.skip("Napplet emitted no envelopes");const t=e.emitted.filter(a=>!a.verdict.ok);if(t.length===0)return u.pass(`${e.emitted.length} envelope(s) valid`);const r=t.map(a=>({envelope:a.envelope,errors:a.verdict.errors}));return u.fail(`${t.length}/${e.emitted.length} emitted envelope(s) invalid`,r)}},{id:"wire/declared-naps-only",area:"wire",severity:"warning",title:"Only emits NAP domains it declared in napplet-requires",run:e=>{if(e.emitted.length===0)return u.skip("Napplet emitted no envelopes");if(!C(e.manifestHtml,"napplet-requires"))return u.skip("No napplet-requires to compare against");const t=new Set(E(e.manifestHtml).requires.map(i=>i.startsWith("nap:")?i.slice(4):i)),a=[...new Set(e.emitted.map(i=>i.verdict.domain).filter(i=>typeof i=="string"))].filter(i=>!t.has(i));return a.length?u.fail(`Emitted undeclared NAP domain(s): ${a.join(", ")}`,a):u.pass()}},{id:"degrade/supports-false",area:"degradation",severity:"error",title:"Does not crash when the NAP-SHELL environment offers no capabilities (supports() false)",run:e=>e.degraded?e.degraded.bootError?u.fail(`Crashed under a no-capability shell: ${e.degraded.bootError}`):u.pass():u.skip("Host did not run the degraded (no-capabilities) pass")},{id:"lifecycle/clean-teardown",area:"lifecycle",severity:"warning",title:"Tears down listeners on unload",run:e=>e.lifecycle?e.lifecycle.listenerLeak?u.fail("Message listeners remained after unload"):u.pass():u.skip("Lifecycle not measured")}];function se(e,t={}){const r=t.now??(()=>Date.now()),a=t.checks??re,i=r(),s=a.map(l=>{const m=l.run(e);return{id:l.id,area:l.area,severity:l.severity,title:l.title,status:m.status,detail:m.detail,diagnostics:m.diagnostics}}),d={total:s.length,passed:s.filter(l=>l.status==="pass").length,failed:s.filter(l=>l.status==="fail").length,skipped:s.filter(l=>l.status==="skip").length,errors:s.filter(l=>l.status==="fail"&&l.severity==="error").length,warnings:s.filter(l=>l.status==="fail"&&l.severity==="warning").length};return{napplet:E(e.manifestHtml).nappletType,startedAt:i,finishedAt:r(),checks:s,summary:d,ok:d.errors===0}}const ne=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,oe="/__conformance__/events",T=new URLSearchParams(location.search),D=T.get("live")==="1";function y(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const g=document.getElementById("app");function le(){return g.innerHTML=`
|
|
2
|
+
<header class="masthead">
|
|
3
|
+
<h1>napplet conformance ${D?'<span class="live-badge" id="live-badge">● live</span>':""}</h1>
|
|
4
|
+
<p>Load a napplet by URL and run NAP protocol conformance live. Same engine as <code>napplet-conformance</code> CI.</p>
|
|
5
|
+
</header>
|
|
6
|
+
<form id="run-form" class="runbar">
|
|
7
|
+
<input id="url" type="url" placeholder="https://localhost:5173/ (a napplet served with permissive CORS)" autocomplete="off" />
|
|
8
|
+
<button id="run" type="submit">Run conformance</button>
|
|
9
|
+
<button id="rerun" type="button" class="secondary" hidden>Re-run</button>
|
|
10
|
+
</form>
|
|
11
|
+
<p id="status" class="status" role="status"></p>
|
|
12
|
+
<section id="output" class="output"></section>
|
|
13
|
+
`,{urlInput: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=le();let h="",L=0;function k(e,t="info"){f.status.textContent=e,f.status.dataset.kind=t}function I(e){return String(e).padStart(2,"0")}function ae(e){const t=new Date(e);return`${I(t.getHours())}:${I(t.getMinutes())}:${I(t.getSeconds())}`}async function de(e){try{const r=await(await fetch(e,{mode:"cors"})).text();return{html:r,forbidden:ne.test(r)?["window.nostr"]:[],fetched:!0}}catch{return{html:"",forbidden:[],fetched:!1}}}function ue(e){const t=e.ok?"conformant":"non-conformant",a=[...new Set(e.checks.map(d=>d.area))].map(d=>{const p=e.checks.filter(l=>l.area===d).map(l=>{const m=l.severity==="warning"&&l.status==="fail"?' <span class="tag warn">warning</span>':"",v=l.detail?`<span class="detail">${y(l.detail)}</span>`:"";return`<li class="check ${l.status}"><span class="badge ${l.status}">${l.status}</span><span class="id">${y(l.id)}</span>${m}${v}</li>`}).join("");return`<div class="area"><h3>${y(d)}</h3><ul>${p}</ul></div>`}).join(""),i=e.summary,s=L>1?`<span class="run-stamp">run #${L} · ${ae(e.finishedAt)}</span>`:"";return`
|
|
14
|
+
<div class="verdict ${t}">
|
|
15
|
+
<span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
|
|
16
|
+
<span class="counts">${i.passed} passed · ${i.failed} failed · ${i.skipped} skipped${i.warnings?` · ${i.warnings} warning(s)`:""}</span>
|
|
17
|
+
${s}
|
|
18
|
+
</div>
|
|
19
|
+
<div class="checks">${a}</div>
|
|
20
|
+
`}function ce(e){return e.length===0?'<p class="empty">No envelopes were emitted.</p>':`<ul class="envlog">${e.map(r=>{const a=r.verdict.ok?"ok":"bad",i=r.verdict.ok?"":`<div class="errs">${r.verdict.errors.map(s=>y(`${s.code}: ${s.message}`)).join("<br>")}</div>`;return`<li class="env ${a}"><code>${y(JSON.stringify(r.envelope))}</code>${i}</li>`}).join("")}</ul>`}function pe(e,t){if(!t)return`<p class="empty">Couldn't fetch the napplet HTML (likely CORS). Serve it with <code>Access-Control-Allow-Origin: *</code>, or use the CLI for the authoritative manifest checks.</p>`;const r=E(e),a=(i,s)=>`<tr><th>${y(i)}</th><td>${s?y(String(s)):"<em>—</em>"}</td></tr>`;return`
|
|
21
|
+
<table class="manifest">
|
|
22
|
+
${a("napplet-type",r.nappletType)}
|
|
23
|
+
${a("requires",r.requires.join(", "))}
|
|
24
|
+
</table>
|
|
25
|
+
${r.errors.length?`<div class="errs">${r.errors.map(i=>y(`${i.code}: ${i.message}`)).join("<br>")}</div>`:""}
|
|
26
|
+
`}function fe(e,t,r,a){f.output.innerHTML=`
|
|
27
|
+
${ue(e)}
|
|
28
|
+
<div class="panels">
|
|
29
|
+
<section class="panel"><h2>Emitted envelopes (${t.length})</h2>${ce(t)}</section>
|
|
30
|
+
<section class="panel"><h2>Manifest</h2>${pe(r,a)}</section>
|
|
31
|
+
</div>
|
|
32
|
+
`}async function $(e){h=e,f.runButton.disabled=!0,f.rerunButton.disabled=!0;try{k(`Fetching manifest for ${e} …`);const{html:t,forbidden:r,fetched:a}=await de(e);k("Booting napplet in a sandboxed iframe …");const i=await X({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),s=Y({manifestHtml:t,boot:i,forbiddenGlobals:r}),d=se(s);L+=1,k(d.ok?"Done — conformant.":"Done — non-conformant.",d.ok?"info":"error"),fe(d,i.emitted,t,a),f.rerunButton.hidden=!1}catch(t){k(`Error: ${t instanceof Error?t.message:String(t)}`,"error")}finally{f.runButton.disabled=!1,f.rerunButton.disabled=!1}}function me(){if(!D||typeof EventSource>"u")return;let e=!1;const t=new EventSource(oe);t.addEventListener("rerun",()=>{!h||e||(e=!0,setTimeout(()=>{e=!1,$(h)},50))}),t.addEventListener("open",()=>{f.liveBadge&&(f.liveBadge.dataset.state="connected")}),t.addEventListener("error",()=>{f.liveBadge&&(f.liveBadge.dataset.state="disconnected")})}f.runButton.parentElement.addEventListener("submit",e=>{e.preventDefault();const t=f.urlInput.value.trim();if(t){const r=new URL(location.href);r.searchParams.set("url",t),history.replaceState(null,"",r),$(t)}});f.rerunButton.addEventListener("click",()=>{h&&$(h)});me();const P=T.get("url");P&&(f.urlInput.value=P,$(P));
|
package/dist/ui/index.html
CHANGED
|
@@ -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-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-DFsEX1km.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.
|
|
3
|
+
"version": "0.2.6",
|
|
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.
|
|
18
|
+
"@napplet/conformance": "^0.6.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.
|
|
24
|
+
"@napplet/conformance-web": "0.0.6"
|
|
25
25
|
},
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"repository": {
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const i of document.querySelectorAll('link[rel="modulepreload"]'))o(i);new MutationObserver(i=>{for(const r of i)if(r.type==="childList")for(const d of r.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&o(d)}).observe(document,{childList:!0,subtree:!0});function s(i){const r={};return i.integrity&&(r.integrity=i.integrity),i.referrerPolicy&&(r.referrerPolicy=i.referrerPolicy),i.crossOrigin==="use-credentials"?r.credentials="include":i.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function o(i){if(i.ep)return;i.ep=!0;const r=s(i);fetch(i.href,r)}})();var P=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent","webrtc","link","serial"],G=["shell"],a={id:"string"},K={"shell.ready":{dir:"out"},"shell.init":{dir:"in"},"relay.subscribe":{dir:"out",fields:{...a,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...a,subId:"string"}},"relay.publish":{dir:"out",fields:{...a,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...a,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...a,filters:"array"}},"relay.event":{dir:"in"},"relay.eose":{dir:"in"},"relay.closed":{dir:"in"},"relay.publish.result":{dir:"in"},"relay.publishEncrypted.result":{dir:"in"},"relay.query.result":{dir:"in"},"identity.getPublicKey":{dir:"out",fields:{...a}},"identity.getRelays":{dir:"out",fields:{...a}},"identity.getProfile":{dir:"out",fields:{...a}},"identity.getFollows":{dir:"out",fields:{...a}},"identity.getList":{dir:"out",fields:{...a,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...a}},"identity.getMutes":{dir:"out",fields:{...a}},"identity.getBlocked":{dir:"out",fields:{...a}},"identity.getBadges":{dir:"out",fields:{...a}},"identity.changed":{dir:"in"},"identity.getPublicKey.result":{dir:"in"},"identity.getRelays.result":{dir:"in"},"identity.getProfile.result":{dir:"in"},"identity.getFollows.result":{dir:"in"},"identity.getList.result":{dir:"in"},"identity.getZaps.result":{dir:"in"},"identity.getMutes.result":{dir:"in"},"identity.getBlocked.result":{dir:"in"},"identity.getBadges.result":{dir:"in"},"storage.get":{dir:"out",fields:{...a,key:"string"}},"storage.set":{dir:"out",fields:{...a,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...a,key:"string"}},"storage.keys":{dir:"out",fields:{...a}},"storage.get.result":{dir:"in"},"storage.set.result":{dir:"in"},"storage.remove.result":{dir:"in"},"storage.keys.result":{dir:"in"},"inc.emit":{dir:"out",fields:{topic:"string"}},"inc.subscribe":{dir:"out",fields:{...a,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...a,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...a}},"inc.channel.close":{dir:"out",fields:{channelId:"string"}},"inc.subscribe.result":{dir:"in"},"inc.event":{dir:"in"},"inc.channel.open.result":{dir:"in"},"inc.channel.event":{dir:"in"},"inc.channel.list.result":{dir:"in"},"inc.channel.closed":{dir:"in"},"theme.get":{dir:"out",fields:{...a}},"theme.get.result":{dir:"in"},"theme.changed":{dir:"in"},"keys.forward":{dir:"out",fields:{key:"string",code:"string",ctrl:"boolean",alt:"boolean",shift:"boolean",meta:"boolean"}},"keys.registerAction":{dir:"out",fields:{...a,action:"object"}},"keys.unregisterAction":{dir:"out",fields:{actionId:"string"}},"keys.registerAction.result":{dir:"in"},"keys.bindings":{dir:"in"},"keys.action":{dir:"in"},"media.session.create":{dir:"out",fields:{...a,owner:"object"}},"media.session.update":{dir:"out",fields:{sessionId:"string",metadata:"object"}},"media.session.destroy":{dir:"out",fields:{sessionId:"string"}},"media.state":{dir:"out",fields:{sessionId:"string",status:"string"}},"media.capabilities":{dir:"out",fields:{sessionId:"string",actions:"array"}},"media.session.create.result":{dir:"in"},"media.command":{dir:"in"},"media.controls":{dir:"in"},"notify.send":{dir:"out",fields:{...a,title:"string"}},"notify.dismiss":{dir:"out",fields:{notificationId:"string"}},"notify.badge":{dir:"out",fields:{count:"number"}},"notify.channel.register":{dir:"out",fields:{channelId:"string",label:"string"}},"notify.permission.request":{dir:"out",fields:{...a}},"notify.send.result":{dir:"in"},"notify.permission.result":{dir:"in"},"notify.action":{dir:"in"},"notify.clicked":{dir:"in"},"notify.dismissed":{dir:"in"},"notify.controls":{dir:"in"},"config.registerSchema":{dir:"out",fields:{...a,schema:"object"}},"config.get":{dir:"out",fields:{...a}},"config.subscribe":{dir:"out"},"config.unsubscribe":{dir:"out"},"config.openSettings":{dir:"out"},"config.registerSchema.result":{dir:"in"},"config.values":{dir:"in"},"config.schemaError":{dir:"in"},"resource.bytes":{dir:"out",fields:{...a,url:"string"}},"resource.cancel":{dir:"out",fields:{...a}},"resource.bytes.result":{dir:"in"},"resource.bytes.error":{dir:"in"},"cvm.discover":{dir:"out",fields:{...a}},"cvm.request":{dir:"out",fields:{...a,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...a,server:"object"}},"cvm.discover.result":{dir:"in"},"cvm.request.result":{dir:"in"},"cvm.close.result":{dir:"in"},"cvm.event":{dir:"in"},"outbox.query":{dir:"out",fields:{...a,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...a,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...a,subId:"string"}},"outbox.publish":{dir:"out",fields:{...a,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...a,target:"present"}},"outbox.query.result":{dir:"in"},"outbox.event":{dir:"in"},"outbox.eose":{dir:"in"},"outbox.closed":{dir:"in"},"outbox.publish.result":{dir:"in"},"outbox.resolveRelays.result":{dir:"in"},"upload.upload":{dir:"out",fields:{...a,request:"object"}},"upload.status":{dir:"out",fields:{...a,uploadId:"string"}},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...a,request:"object"}},"intent.available":{dir:"out",fields:{...a,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...a}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"},"webrtc.open":{dir:"out",fields:{...a,request:"object"}},"webrtc.send":{dir:"out",fields:{...a,sessionId:"string",payload:"present"}},"webrtc.close":{dir:"out",fields:{...a,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:{...a,url:"string"}},"link.open.result":{dir:"in"},"serial.open":{dir:"out",fields:{...a,request:"object"}},"serial.write":{dir:"out",fields:{...a,sessionId:"string",data:"array"}},"serial.close":{dir:"out",fields:{...a,sessionId:"string"}},"serial.open.result":{dir:"in"},"serial.write.result":{dir:"in"},"serial.close.result":{dir:"in"},"serial.event":{dir:"in"}};function M(e){if(e===void 0)return"undefined";if(e===null)return"null";if(Array.isArray(e))return"array";const t=typeof e;return t==="string"||t==="number"||t==="boolean"?t:t==="object"?"object":"present"}function U(e,t){return t==="present"?e!=null:M(e)===t}function Z(e){const t=[];if(typeof e!="object"||e===null||Array.isArray(e))return{ok:!1,errors:[{code:"not-an-object",message:"Envelope must be a non-null object"}]};const s=e,o=s.type;if(typeof o!="string")return{ok:!1,errors:[{code:"missing-type",message:"Envelope is missing a string `type` field"}]};const i=o.indexOf(".");if(i<=0)return{ok:!1,type:o,errors:[{code:"malformed-type",message:`Envelope type "${o}" is not in domain.action form`}]};const r=o.slice(0,i);if(!(P.includes(r)||G.includes(r)))return{ok:!1,type:o,domain:r,errors:[{code:"unknown-domain",message:`"${r}" is not a known NAP domain`}]};const p=K[o];if(!p)return{ok:!1,type:o,domain:r,errors:[{code:"unknown-type",message:`"${o}" is not a known ${r} message type`}]};if(p.dir==="in")return{ok:!1,type:o,domain:r,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${o}" is a shell→napplet message; a napplet must not emit it`}]};for(const[n,y]of Object.entries(p.fields??{})){if(!(n in s)||s[n]===void 0){t.push({code:"missing-field",message:`Required field "${n}" is missing`,field:n});continue}U(s[n],y)||t.push({code:"wrong-type",message:`Field "${n}" should be ${y} but is ${M(s[n])}`,field:n})}return{ok:t.length===0,type:o,domain:r,direction:"out",errors:t}}var R=/^[a-z0-9][a-z0-9._:-]*$/;function z(e){return e.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g,(t,s)=>{if(s[0]==="#"){const i=s[1]==="x"||s[1]==="X"?parseInt(s.slice(2),16):parseInt(s.slice(1),10);return Number.isFinite(i)?String.fromCodePoint(i):t}return{amp:"&",lt:"<",gt:">",quot:'"',apos:"'"}[s]??t})}function S(e,t){const s=/<meta\b[^>]*>/gi;let o;for(;(o=s.exec(e))!==null;){const i=o[0],r=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);if((r?.[1]??r?.[2]??"").trim()!==t)continue;const p=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(i);return z((p?.[1]??p?.[2]??"").trim())}}function I(e){if(Array.isArray(e))return e.some(I);if(e&&typeof e=="object"){const t=e;return"pattern"in t?!0:Object.values(t).some(I)}return!1}function k(e,t={}){const s=[],o=[],i=S(e,"napplet-type");i?R.test(i)||s.push({code:"invalid-napplet-type",message:`napplet-type "${i}" must match ${R}`}):s.push({code:"missing-napplet-type",message:'No <meta name="napplet-type"> declared'});const r=S(e,"napplet-requires"),d=r?r.split(",").map(n=>n.trim()).filter(Boolean):[];for(const n of d){const y=n.startsWith("nap:")?n.slice(4):n;P.includes(y)||s.push({code:"unknown-required-nap",message:`napplet-requires lists "${n}" which is not a known NAP domain`})}const p=S(e,"napplet-config-schema");if(p){let n;try{n=JSON.parse(p)}catch{n=void 0,s.push({code:"invalid-config-schema",message:"napplet-config-schema is not valid JSON"})}n!==void 0&&(typeof n!="object"||n===null||Array.isArray(n)?s.push({code:"invalid-config-schema",message:"napplet-config-schema must be a JSON Schema object"}):I(n)&&s.push({code:"invalid-config-schema",message:"napplet-config-schema uses the `pattern` keyword, which the draft-07 core subset excludes (CVE-2025-69873)"}))}return{ok:s.length===0,nappletType:i,requires:d,errors:s,warnings:o}}var j="f".repeat(64),J="https://reference.invalid/blob",l=e=>[e],u=()=>[],V={"relay.subscribe":e=>l({type:"relay.eose",subId:e.subId}),"relay.close":u,"relay.publish":e=>l({type:"relay.publish.result",id:e.id,ok:!0,event:e.event}),"relay.publishEncrypted":e=>l({type:"relay.publishEncrypted.result",id:e.id,ok:!0,event:e.event}),"relay.query":e=>l({type:"relay.query.result",id:e.id,events:[]}),"identity.getPublicKey":e=>l({type:"identity.getPublicKey.result",id:e.id,pubkey:j}),"identity.getRelays":e=>l({type:"identity.getRelays.result",id:e.id,relays:{}}),"identity.getProfile":e=>l({type:"identity.getProfile.result",id:e.id,profile:null}),"identity.getFollows":e=>l({type:"identity.getFollows.result",id:e.id,pubkeys:[]}),"identity.getList":e=>l({type:"identity.getList.result",id:e.id,entries:[]}),"identity.getZaps":e=>l({type:"identity.getZaps.result",id:e.id,zaps:[]}),"identity.getMutes":e=>l({type:"identity.getMutes.result",id:e.id,pubkeys:[]}),"identity.getBlocked":e=>l({type:"identity.getBlocked.result",id:e.id,pubkeys:[]}),"identity.getBadges":e=>l({type:"identity.getBadges.result",id:e.id,badges:[]}),"storage.get":e=>l({type:"storage.get.result",id:e.id,value:null}),"storage.set":e=>l({type:"storage.set.result",id:e.id}),"storage.remove":e=>l({type:"storage.remove.result",id:e.id}),"storage.keys":e=>l({type:"storage.keys.result",id:e.id,keys:[]}),"inc.emit":u,"inc.subscribe":e=>l({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":u,"inc.channel.open":e=>l({type:"inc.channel.open.result",id:e.id,channelId:`chan-${String(e.id)}`,peer:"reference-peer"}),"inc.channel.emit":u,"inc.channel.broadcast":u,"inc.channel.list":e=>l({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":u,"theme.get":e=>l({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":u,"keys.registerAction":e=>l({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":u,"media.session.create":e=>l({type:"media.session.create.result",id:e.id,sessionId:`session-${String(e.id)}`,owner:e.owner}),"media.session.update":u,"media.session.destroy":u,"media.state":u,"media.capabilities":u,"notify.send":e=>l({type:"notify.send.result",id:e.id,notificationId:`notif-${String(e.id)}`}),"notify.dismiss":u,"notify.badge":u,"notify.channel.register":u,"notify.permission.request":e=>l({type:"notify.permission.result",id:e.id,granted:!0}),"config.registerSchema":e=>l({type:"config.registerSchema.result",id:e.id,ok:!0}),"config.get":e=>l({type:"config.values",id:e.id,values:{}}),"config.subscribe":()=>l({type:"config.values",values:{}}),"config.unsubscribe":u,"config.openSettings":u,"resource.bytes":e=>l({type:"resource.bytes.result",id:e.id,blob:new Blob([]),mime:"application/octet-stream"}),"resource.cancel":u,"cvm.discover":e=>l({type:"cvm.discover.result",id:e.id,servers:[]}),"cvm.request":e=>l({type:"cvm.request.result",id:e.id,message:{}}),"cvm.close":e=>l({type:"cvm.close.result",id:e.id}),"outbox.query":e=>l({type:"outbox.query.result",id:e.id,events:[],relays:{}}),"outbox.subscribe":e=>l({type:"outbox.eose",subId:e.subId}),"outbox.close":u,"outbox.publish":e=>l({type:"outbox.publish.result",id:e.id,ok:!0}),"outbox.resolveRelays":e=>l({type:"outbox.resolveRelays.result",id:e.id,plan:{}}),"upload.upload":e=>l({type:"upload.upload.result",id:e.id,result:{url:J}}),"upload.status":e=>l({type:"upload.status.result",id:e.id,status:{}}),"intent.invoke":e=>l({type:"intent.invoke.result",id:e.id,result:{ok:!0,archetype:"reference",action:"open",handled:!0}}),"intent.available":e=>l({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=>l({type:"intent.handlers.result",id:e.id,handlers:[]}),"webrtc.open":e=>l({type:"webrtc.open.result",id:e.id,session:{id:"webrtc-reference",scope:{type:"direct",pubkey:j},channel:"default",state:"connecting"}}),"webrtc.send":e=>l({type:"webrtc.send.result",id:e.id}),"webrtc.close":e=>l({type:"webrtc.close.result",id:e.id}),"link.open":e=>l({type:"link.open.result",id:e.id,status:"opened"}),"serial.open":e=>l({type:"serial.open.result",id:e.id,session:{id:`serial-${String(e.id)}`,state:"open"}}),"serial.write":e=>l({type:"serial.write.result",id:e.id}),"serial.close":e=>l({type:"serial.close.result",id:e.id})};function W(e={}){const t=e.now??(()=>Date.now()),s={domains:e.capabilities?.domains??[...P],protocols:e.capabilities?.protocols??{}},o=e.services??[],i=[];function r(d){const p=d&&typeof d=="object"&&typeof d.type=="string"?d.type:void 0;if(p==="shell.ready")return[{type:"shell.init",capabilities:{domains:s.domains,protocols:s.protocols},services:o}];if(i.push({envelope:d,verdict:Z(d),timestamp:t()}),!p)return[];const n=V[p];return n?n(d):[]}return{capabilities:s,services:o,get records(){return i},handle:r,reset(){i.length=0}}}function Y(e){return{manifestHtml:e.manifestHtml,sandbox:e.sandbox??{allowScripts:!0,allowSameOrigin:!1},installedGlobal:e.boot.installedGlobal,bootError:e.boot.bootError,emitted:e.boot.emitted,forbiddenGlobals:e.forbiddenGlobals??[],degraded:e.boot.degraded,lifecycle:e.lifecycle??null}}function x(e){return new Promise(t=>setTimeout(t,e))}async function B(e,t,s,o){const i=W(e?{capabilities:e}:void 0),r=s.createElement("iframe");r.setAttribute("sandbox",t.allowSameOrigin?"allow-scripts allow-same-origin":"allow-scripts"),r.setAttribute("aria-hidden","true"),r.style.position="absolute",r.style.width="1px",r.style.height="1px",r.style.left="-9999px";let d;const p=new Promise(b=>{d=b}),n=b=>{if(b.source!==r.contentWindow)return;const H=i.handle(b.data),F=b.source;for(const _ of H)F?.postMessage(_,"*");const $=b.data;$&&typeof $=="object"&&$.type==="shell.ready"&&d()};o.addEventListener("message",n),(s.body??s.documentElement).appendChild(r),r.setAttribute("src",t.url);let y=!1,v=null;const O=Symbol("timeout");await Promise.race([p.then(()=>"ready"),x(t.readyTimeoutMs).then(()=>O)])===O?v=`napplet did not boot (no shell.ready within ${t.readyTimeoutMs}ms; a conformant napplet must run under sandbox="allow-scripts" without allow-same-origin)`:(y=!0,await x(t.settleMs)),o.removeEventListener("message",n);try{r.remove()}catch{}return{installedGlobal:y,bootError:v,emitted:[...i.records]}}async function X(e){const t=e.document??document,s=e.window??window,o={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},i=await B(void 0,o,t,s);let r=null;{const d=await B({domains:[],protocols:{}},o,t,s);r={bootError:d.bootError,emitted:d.emitted}}return{installedGlobal:i.installedGlobal,bootError:i.bootError,emitted:i.emitted,degraded:r}}var Q=e=>({status:"pass",detail:e}),ee=(e,t)=>({status:"fail",detail:e,diagnostics:t}),te=e=>({status:"skip",detail:e}),c={pass:Q,fail:ee,skip:te};function ie(e,t){return k(e).errors.filter(s=>t.includes(s.code))}function C(e,t){return new RegExp(`name\\s*=\\s*["']${t}["']`,"i").test(e)}function A(e,t,s,o){return{id:e,area:"manifest",severity:"error",title:t,run:i=>{if(o&&!C(i.manifestHtml,o))return c.skip(`No ${o} declared`);const r=ie(i.manifestHtml,s);return r.length?c.fail(r[0].message,r):c.pass()}}}var se=[A("manifest/napplet-type","Declares a valid napplet-type",["missing-napplet-type","invalid-napplet-type"]),A("manifest/declared-naps","napplet-requires lists only real NAP domains",["unknown-required-nap"],"napplet-requires"),A("manifest/config-schema","Config schema is a draft-07 core subset",["invalid-config-schema"],"napplet-config-schema"),{id:"boot/sandbox-allow-scripts",area:"boot",severity:"error",title:"Runs under allow-scripts without allow-same-origin",run:e=>e.sandbox.allowScripts?e.sandbox.allowSameOrigin?c.fail("Napplet relied on allow-same-origin, which a shell must not grant"):c.pass():c.fail("Napplet was not loaded with allow-scripts")},{id:"boot/installs-global",area:"boot",severity:"error",title:"Completes the NAP-SHELL handshake (posts shell.ready)",run:e=>e.installedGlobal?c.pass():c.fail("window.napplet was not present after load (no NAP-SHELL shell.ready)")},{id:"boot/no-boot-error",area:"boot",severity:"error",title:"Boots without an uncaught error (observed via NAP-SHELL shell.ready)",run:e=>e.bootError?c.fail(e.bootError):c.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?c.fail(`Accessed forbidden global(s): ${e.forbiddenGlobals.join(", ")}`,e.forbiddenGlobals):c.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 c.skip("Napplet emitted no envelopes");const t=e.emitted.filter(o=>!o.verdict.ok);if(t.length===0)return c.pass(`${e.emitted.length} envelope(s) valid`);const s=t.map(o=>({envelope:o.envelope,errors:o.verdict.errors}));return c.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 c.skip("Napplet emitted no envelopes");if(!C(e.manifestHtml,"napplet-requires"))return c.skip("No napplet-requires to compare against");const t=new Set(k(e.manifestHtml).requires.map(i=>i.startsWith("nap:")?i.slice(4):i)),o=[...new Set(e.emitted.map(i=>i.verdict.domain).filter(i=>typeof i=="string"))].filter(i=>!t.has(i));return o.length?c.fail(`Emitted undeclared NAP domain(s): ${o.join(", ")}`,o):c.pass()}},{id:"degrade/supports-false",area:"degradation",severity:"error",title:"Does not crash when the NAP-SHELL environment offers no capabilities (supports() false)",run:e=>e.degraded?e.degraded.bootError?c.fail(`Crashed under a no-capability shell: ${e.degraded.bootError}`):c.pass():c.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?c.fail("Message listeners remained after unload"):c.pass():c.skip("Lifecycle not measured")}];function re(e,t={}){const s=t.now??(()=>Date.now()),o=t.checks??se,i=s(),r=o.map(n=>{const y=n.run(e);return{id:n.id,area:n.area,severity:n.severity,title:n.title,status:y.status,detail:y.detail,diagnostics:y.diagnostics}}),d={total:r.length,passed:r.filter(n=>n.status==="pass").length,failed:r.filter(n=>n.status==="fail").length,skipped:r.filter(n=>n.status==="skip").length,errors:r.filter(n=>n.status==="fail"&&n.severity==="error").length,warnings:r.filter(n=>n.status==="fail"&&n.severity==="warning").length};return{napplet:k(e.manifestHtml).nappletType,startedAt:i,finishedAt:s(),checks:r,summary:d,ok:d.errors===0}}const ne=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,oe="/__conformance__/events",T=new URLSearchParams(location.search),D=T.get("live")==="1";function m(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const g=document.getElementById("app");function ae(){return g.innerHTML=`
|
|
2
|
-
<header class="masthead">
|
|
3
|
-
<h1>napplet conformance ${D?'<span class="live-badge" id="live-badge">● live</span>':""}</h1>
|
|
4
|
-
<p>Load a napplet by URL and run NAP protocol conformance live. Same engine as <code>napplet-conformance</code> CI.</p>
|
|
5
|
-
</header>
|
|
6
|
-
<form id="run-form" class="runbar">
|
|
7
|
-
<input id="url" type="url" placeholder="https://localhost:5173/ (a napplet served with permissive CORS)" autocomplete="off" />
|
|
8
|
-
<button id="run" type="submit">Run conformance</button>
|
|
9
|
-
<button id="rerun" type="button" class="secondary" hidden>Re-run</button>
|
|
10
|
-
</form>
|
|
11
|
-
<p id="status" class="status" role="status"></p>
|
|
12
|
-
<section id="output" class="output"></section>
|
|
13
|
-
`,{urlInput:g.querySelector("#url"),runButton:g.querySelector("#run"),rerunButton:g.querySelector("#rerun"),liveBadge:g.querySelector("#live-badge")??document.createElement("span"),status:g.querySelector("#status"),output:g.querySelector("#output")}}const f=ae();let h="",L=0;function w(e,t="info"){f.status.textContent=e,f.status.dataset.kind=t}function N(e){return String(e).padStart(2,"0")}function le(e){const t=new Date(e);return`${N(t.getHours())}:${N(t.getMinutes())}:${N(t.getSeconds())}`}async function de(e){try{const s=await(await fetch(e,{mode:"cors"})).text();return{html:s,forbidden:ne.test(s)?["window.nostr"]:[],fetched:!0}}catch{return{html:"",forbidden:[],fetched:!1}}}function ce(e){const t=e.ok?"conformant":"non-conformant",o=[...new Set(e.checks.map(d=>d.area))].map(d=>{const p=e.checks.filter(n=>n.area===d).map(n=>{const y=n.severity==="warning"&&n.status==="fail"?' <span class="tag warn">warning</span>':"",v=n.detail?`<span class="detail">${m(n.detail)}</span>`:"";return`<li class="check ${n.status}"><span class="badge ${n.status}">${n.status}</span><span class="id">${m(n.id)}</span>${y}${v}</li>`}).join("");return`<div class="area"><h3>${m(d)}</h3><ul>${p}</ul></div>`}).join(""),i=e.summary,r=L>1?`<span class="run-stamp">run #${L} · ${le(e.finishedAt)}</span>`:"";return`
|
|
14
|
-
<div class="verdict ${t}">
|
|
15
|
-
<span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
|
|
16
|
-
<span class="counts">${i.passed} passed · ${i.failed} failed · ${i.skipped} skipped${i.warnings?` · ${i.warnings} warning(s)`:""}</span>
|
|
17
|
-
${r}
|
|
18
|
-
</div>
|
|
19
|
-
<div class="checks">${o}</div>
|
|
20
|
-
`}function ue(e){return e.length===0?'<p class="empty">No envelopes were emitted.</p>':`<ul class="envlog">${e.map(s=>{const o=s.verdict.ok?"ok":"bad",i=s.verdict.ok?"":`<div class="errs">${s.verdict.errors.map(r=>m(`${r.code}: ${r.message}`)).join("<br>")}</div>`;return`<li class="env ${o}"><code>${m(JSON.stringify(s.envelope))}</code>${i}</li>`}).join("")}</ul>`}function pe(e,t){if(!t)return`<p class="empty">Couldn't fetch the napplet HTML (likely CORS). Serve it with <code>Access-Control-Allow-Origin: *</code>, or use the CLI for the authoritative manifest checks.</p>`;const s=k(e),o=(i,r)=>`<tr><th>${m(i)}</th><td>${r?m(String(r)):"<em>—</em>"}</td></tr>`;return`
|
|
21
|
-
<table class="manifest">
|
|
22
|
-
${o("napplet-type",s.nappletType)}
|
|
23
|
-
${o("requires",s.requires.join(", "))}
|
|
24
|
-
</table>
|
|
25
|
-
${s.errors.length?`<div class="errs">${s.errors.map(i=>m(`${i.code}: ${i.message}`)).join("<br>")}</div>`:""}
|
|
26
|
-
`}function fe(e,t,s,o){f.output.innerHTML=`
|
|
27
|
-
${ce(e)}
|
|
28
|
-
<div class="panels">
|
|
29
|
-
<section class="panel"><h2>Emitted envelopes (${t.length})</h2>${ue(t)}</section>
|
|
30
|
-
<section class="panel"><h2>Manifest</h2>${pe(s,o)}</section>
|
|
31
|
-
</div>
|
|
32
|
-
`}async function E(e){h=e,f.runButton.disabled=!0,f.rerunButton.disabled=!0;try{w(`Fetching manifest for ${e} …`);const{html:t,forbidden:s,fetched:o}=await de(e);w("Booting napplet in a sandboxed iframe …");const i=await X({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),r=Y({manifestHtml:t,boot:i,forbiddenGlobals:s}),d=re(r);L+=1,w(d.ok?"Done — conformant.":"Done — non-conformant.",d.ok?"info":"error"),fe(d,i.emitted,t,o),f.rerunButton.hidden=!1}catch(t){w(`Error: ${t instanceof Error?t.message:String(t)}`,"error")}finally{f.runButton.disabled=!1,f.rerunButton.disabled=!1}}function ye(){if(!D||typeof EventSource>"u")return;let e=!1;const t=new EventSource(oe);t.addEventListener("rerun",()=>{!h||e||(e=!0,setTimeout(()=>{e=!1,E(h)},50))}),t.addEventListener("open",()=>{f.liveBadge&&(f.liveBadge.dataset.state="connected")}),t.addEventListener("error",()=>{f.liveBadge&&(f.liveBadge.dataset.state="disconnected")})}f.runButton.parentElement.addEventListener("submit",e=>{e.preventDefault();const t=f.urlInput.value.trim();if(t){const s=new URL(location.href);s.searchParams.set("url",t),history.replaceState(null,"",s),E(t)}});f.rerunButton.addEventListener("click",()=>{h&&E(h)});ye();const q=T.get("url");q&&(f.urlInput.value=q,E(q));
|