@napplet/conformance-cli 0.1.0 → 0.1.1

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.
@@ -347,13 +347,22 @@ var NappletConformanceHostBundle = (() => {
347
347
  return { ok: errors.length === 0, type, domain, direction: "out", errors };
348
348
  }
349
349
  var NAPPLET_TYPE_RE = /^[a-z0-9][a-z0-9._:-]*$/;
350
- var AGGREGATE_HASH_RE = /^[0-9a-f]{64}$/;
351
350
  var NON_EXECUTING_SCRIPT_TYPES = /* @__PURE__ */ new Set([
352
351
  "application/json",
353
352
  "application/ld+json",
354
353
  "importmap",
355
354
  "speculationrules"
356
355
  ]);
356
+ function decodeHtmlEntities(value) {
357
+ return value.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match, entity) => {
358
+ if (entity[0] === "#") {
359
+ const code = entity[1] === "x" || entity[1] === "X" ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
360
+ return Number.isFinite(code) ? String.fromCodePoint(code) : match;
361
+ }
362
+ const named = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
363
+ return named[entity] ?? match;
364
+ });
365
+ }
357
366
  function readMeta(html, name) {
358
367
  const tagRe = /<meta\b[^>]*>/gi;
359
368
  let tag;
@@ -363,7 +372,7 @@ var NappletConformanceHostBundle = (() => {
363
372
  const metaName = (nameMatch?.[1] ?? nameMatch?.[2] ?? "").trim();
364
373
  if (metaName !== name) continue;
365
374
  const contentMatch = /\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(block);
366
- return (contentMatch?.[1] ?? contentMatch?.[2] ?? "").trim();
375
+ return decodeHtmlEntities((contentMatch?.[1] ?? contentMatch?.[2] ?? "").trim());
367
376
  }
368
377
  return void 0;
369
378
  }
@@ -398,8 +407,7 @@ var NappletConformanceHostBundle = (() => {
398
407
  }
399
408
  return false;
400
409
  }
401
- function validateManifest(html, options = {}) {
402
- const requireAggregateHash = options.requireAggregateHash ?? true;
410
+ function validateManifest(html, _options = {}) {
403
411
  const errors = [];
404
412
  const warnings = [];
405
413
  const nappletType = readMeta(html, "napplet-type");
@@ -408,14 +416,6 @@ var NappletConformanceHostBundle = (() => {
408
416
  } else if (!NAPPLET_TYPE_RE.test(nappletType)) {
409
417
  errors.push({ code: "invalid-napplet-type", message: `napplet-type "${nappletType}" must match ${NAPPLET_TYPE_RE}` });
410
418
  }
411
- const aggregateHash = readMeta(html, "napplet-aggregate-hash");
412
- if (!aggregateHash) {
413
- if (requireAggregateHash) {
414
- errors.push({ code: "missing-aggregate-hash", message: 'No <meta name="napplet-aggregate-hash"> \u2014 the build plugin injects it; build before testing' });
415
- }
416
- } else if (!AGGREGATE_HASH_RE.test(aggregateHash)) {
417
- errors.push({ code: "invalid-aggregate-hash", message: `napplet-aggregate-hash "${aggregateHash}" is not a 64-char lowercase hex digest` });
418
- }
419
419
  const requiresRaw = readMeta(html, "napplet-requires");
420
420
  const requires = requiresRaw ? requiresRaw.split(",").map((s) => s.trim()).filter(Boolean) : [];
421
421
  for (const req of requires) {
@@ -458,7 +458,6 @@ var NappletConformanceHostBundle = (() => {
458
458
  return {
459
459
  ok: errors.length === 0,
460
460
  nappletType,
461
- aggregateHash,
462
461
  requires,
463
462
  connectOrigins,
464
463
  errors,
@@ -665,7 +664,6 @@ var NappletConformanceHostBundle = (() => {
665
664
  var CHECKS = [
666
665
  // ── manifest ───────────────────────────────────────────────────────────────
667
666
  manifestCheck("manifest/napplet-type", "Declares a valid napplet-type", ["missing-napplet-type", "invalid-napplet-type"]),
668
- manifestCheck("manifest/aggregate-hash", "Build injected a valid aggregate hash", ["missing-aggregate-hash", "invalid-aggregate-hash"]),
669
667
  manifestCheck("manifest/declared-naps", "napplet-requires lists only real NAP domains", ["unknown-required-nap"], "napplet-requires"),
670
668
  manifestCheck("manifest/config-schema", "Config schema is a draft-07 core subset", ["invalid-config-schema"], "napplet-config-schema"),
671
669
  manifestCheck("manifest/connect-origins", "Declared connect origins are valid", ["invalid-connect-origin"], "napplet-connect-requires"),
@@ -0,0 +1,33 @@
1
+ (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const s of document.querySelectorAll('link[rel="modulepreload"]'))n(s);new MutationObserver(s=>{for(const r of s)if(r.type==="childList")for(const d of r.addedNodes)d.tagName==="LINK"&&d.rel==="modulepreload"&&n(d)}).observe(document,{childList:!0,subtree:!0});function i(s){const r={};return s.integrity&&(r.integrity=s.integrity),s.referrerPolicy&&(r.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?r.credentials="include":s.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function n(s){if(s.ep)return;s.ep=!0;const r=i(s);fetch(s.href,r)}})();var T=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","connect","class","cvm","outbox","upload","intent"],f="[@napplet/nap/connect]",K=new Set(["https","wss","http","ws"]),z={https:"443",wss:"443",http:"80",ws:"80"};function W(e){const t=e.indexOf("://");if(t<0)throw new Error(`${f} origin missing scheme separator '://': ${e}`);const i=e.slice(0,t);if(i!==i.toLowerCase())throw new Error(`${f} scheme must be lowercase: ${e}`);if(!K.has(i))throw new Error(`${f} scheme must be one of https|wss|http|ws: ${e}`);return{scheme:i,rest:e.slice(t+3)}}function V(e,t){if(e.includes("/"))throw new Error(`${f} origin MUST NOT contain a path: ${t}`);if(e.includes("?"))throw new Error(`${f} origin MUST NOT contain a query: ${t}`);if(e.includes("#"))throw new Error(`${f} origin MUST NOT contain a fragment: ${t}`);if(e.includes("*"))throw new Error(`${f} wildcard origins are not permitted: ${t}`);if(e.startsWith("["))throw new Error(`${f} IPv6 literal origins are out of v1 scope: ${t}`)}function Z(e,t,i){let n=t;const s=t.lastIndexOf(":");if(s>=0){n=t.slice(0,s);const r=t.slice(s+1);if(!/^[0-9]+$/.test(r))throw new Error(`${f} port must be decimal digits: ${i}`);const d=Number(r);if(d<1||d>65535)throw new Error(`${f} port out of range 1-65535: ${i}`);if(z[e]===r)throw new Error(`${f} default port ${r} MUST be omitted for scheme ${e}: ${i}`)}return J(n,i),n}function J(e,t){if(e.includes(":"))throw new Error(`${f} IPv6 literal origins are out of v1 scope: ${t}`);if(e.length===0)throw new Error(`${f} host is empty: ${t}`);if(e!==e.toLowerCase())throw new Error(`${f} host MUST be lowercase: ${t}`);if(!/^[\x00-\x7F]+$/.test(e))throw new Error(`${f} non-ASCII host MUST be Punycode-encoded (xn--...) before calling: ${t}`)}function X(e,t){if(!/^[0-9]+(\.[0-9]+){3}$/.test(e))return!1;for(const i of e.split(".")){if(i.length>1&&i.startsWith("0"))throw new Error(`${f} IPv4 octet MUST NOT have leading zeros: ${t}`);const n=Number(i);if(!Number.isInteger(n)||n<0||n>255)throw new Error(`${f} IPv4 octet out of range 0-255: ${t}`)}return!0}function Y(e,t){for(const i of e.split(".")){if(i.length===0||i.length>63)throw new Error(`${f} invalid host label length: ${t}`);if(!/^[a-z0-9-]+$/.test(i))throw new Error(`${f} host label contains invalid characters: ${t}`);if(i.startsWith("-")||i.endsWith("-"))throw new Error(`${f} host label MUST NOT start or end with hyphen: ${t}`)}}function Q(e){if(typeof e!="string"||e.length===0)throw new Error(`${f} origin must be a non-empty string`);const{scheme:t,rest:i}=W(e);V(i,e);const n=Z(t,i,e);return X(n,e)||Y(n,e),e}var o={id:"string"},ee={"relay.subscribe":{dir:"out",fields:{...o,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...o,subId:"string"}},"relay.publish":{dir:"out",fields:{...o,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...o,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...o,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:{...o}},"identity.getRelays":{dir:"out",fields:{...o}},"identity.getProfile":{dir:"out",fields:{...o}},"identity.getFollows":{dir:"out",fields:{...o}},"identity.getList":{dir:"out",fields:{...o,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...o}},"identity.getMutes":{dir:"out",fields:{...o}},"identity.getBlocked":{dir:"out",fields:{...o}},"identity.getBadges":{dir:"out",fields:{...o}},"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:{...o,key:"string"}},"storage.set":{dir:"out",fields:{...o,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...o,key:"string"}},"storage.keys":{dir:"out",fields:{...o}},"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:{...o,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...o,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...o}},"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:{...o}},"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:{...o,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:{...o,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:{...o,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:{...o}},"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:{...o,schema:"object"}},"config.get":{dir:"out",fields:{...o}},"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:{...o,url:"string"}},"resource.cancel":{dir:"out",fields:{...o}},"resource.bytes.result":{dir:"in"},"resource.bytes.error":{dir:"in"},"class.assigned":{dir:"in"},"cvm.discover":{dir:"out",fields:{...o}},"cvm.request":{dir:"out",fields:{...o,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...o,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:{...o,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...o,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...o,subId:"string"}},"outbox.publish":{dir:"out",fields:{...o,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...o,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:{...o,request:"object"}},"upload.status":{dir:"out",fields:{...o,uploadId:"string"}},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...o,request:"object"}},"intent.available":{dir:"out",fields:{...o,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...o}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"}};function j(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 te(e,t){return t==="present"?e!=null:j(e)===t}function ie(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 i=e,n=i.type;if(typeof n!="string")return{ok:!1,errors:[{code:"missing-type",message:"Envelope is missing a string `type` field"}]};const s=n.indexOf(".");if(s<=0)return{ok:!1,type:n,errors:[{code:"malformed-type",message:`Envelope type "${n}" is not in domain.action form`}]};const r=n.slice(0,s);if(!T.includes(r))return{ok:!1,type:n,domain:r,errors:[{code:"unknown-domain",message:`"${r}" is not a known NAP domain`}]};const d=ee[n];if(!d)return{ok:!1,type:n,domain:r,errors:[{code:"unknown-type",message:`"${n}" is not a known ${r} message type`}]};if(d.dir==="in")return{ok:!1,type:n,domain:r,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${n}" is a shell→napplet message; a napplet must not emit it`}]};for(const[u,l]of Object.entries(d.fields??{})){if(!(u in i)||i[u]===void 0){t.push({code:"missing-field",message:`Required field "${u}" is missing`,field:u});continue}te(i[u],l)||t.push({code:"wrong-type",message:`Field "${u}" should be ${l} but is ${j(i[u])}`,field:u})}return{ok:t.length===0,type:n,domain:r,direction:"out",errors:t}}var M=/^[a-z0-9][a-z0-9._:-]*$/,se=new Set(["application/json","application/ld+json","importmap","speculationrules"]);function re(e){return e.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g,(t,i)=>{if(i[0]==="#"){const s=i[1]==="x"||i[1]==="X"?parseInt(i.slice(2),16):parseInt(i.slice(1),10);return Number.isFinite(s)?String.fromCodePoint(s):t}return{amp:"&",lt:"<",gt:">",quot:'"',apos:"'"}[i]??t})}function k(e,t){const i=/<meta\b[^>]*>/gi;let n;for(;(n=i.exec(e))!==null;){const s=n[0],r=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(s);if((r?.[1]??r?.[2]??"").trim()!==t)continue;const u=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(s);return re((u?.[1]??u?.[2]??"").trim())}}function ne(e){const t=e.replace(/<!--[\s\S]*?-->/g,""),i=/<script\b([\s\S]*?)>/gi,n=[];let s;for(;(s=i.exec(t))!==null;){const r=s[0],d=s[1]??"",u=/\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(d);if(u){if((u[1]??u[2]??"").trim().length>0)continue;n.push(r);continue}const l=/\btype\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(d),y=(l?.[1]??l?.[2]??"").trim().toLowerCase();y&&se.has(y)||n.push(r)}return n}function R(e){if(Array.isArray(e))return e.some(R);if(e&&typeof e=="object"){const t=e;return"pattern"in t?!0:Object.values(t).some(R)}return!1}function N(e,t={}){const i=[],n=[],s=k(e,"napplet-type");s?M.test(s)||i.push({code:"invalid-napplet-type",message:`napplet-type "${s}" must match ${M}`}):i.push({code:"missing-napplet-type",message:'No <meta name="napplet-type"> declared'});const r=k(e,"napplet-requires"),d=r?r.split(",").map(p=>p.trim()).filter(Boolean):[];for(const p of d){const $=p.startsWith("nap:")?p.slice(4):p;T.includes($)||i.push({code:"unknown-required-nap",message:`napplet-requires lists "${p}" which is not a known NAP domain`})}const u=k(e,"napplet-config-schema");if(u){let p;try{p=JSON.parse(u)}catch{p=void 0,i.push({code:"invalid-config-schema",message:"napplet-config-schema is not valid JSON"})}p!==void 0&&(typeof p!="object"||p===null||Array.isArray(p)?i.push({code:"invalid-config-schema",message:"napplet-config-schema must be a JSON Schema object"}):R(p)&&i.push({code:"invalid-config-schema",message:"napplet-config-schema uses the `pattern` keyword, which the draft-07 core subset excludes (CVE-2025-69873)"}))}const l=k(e,"napplet-connect-requires"),y=l?l.split(/\s+/).map(p=>p.trim()).filter(Boolean):[],b=[];for(const p of y)try{b.push(Q(p))}catch($){const h=$ instanceof Error?$.message:String($);i.push({code:"invalid-connect-origin",message:`connect origin "${p}" is invalid: ${h}`})}for(const p of ne(e))i.push({code:"inline-script",message:`Inline <script> is forbidden under script-src 'self': ${p}`});return{ok:i.length===0,nappletType:s,requires:d,connectOrigins:b,errors:i,warnings:n}}var oe="f".repeat(64),ae="https://reference.invalid/blob",a=e=>[e],m=()=>[],le={"relay.subscribe":e=>a({type:"relay.eose",subId:e.subId}),"relay.close":m,"relay.publish":e=>a({type:"relay.publish.result",id:e.id,ok:!0,event:e.event}),"relay.publishEncrypted":e=>a({type:"relay.publishEncrypted.result",id:e.id,ok:!0,event:e.event}),"relay.query":e=>a({type:"relay.query.result",id:e.id,events:[]}),"identity.getPublicKey":e=>a({type:"identity.getPublicKey.result",id:e.id,pubkey:oe}),"identity.getRelays":e=>a({type:"identity.getRelays.result",id:e.id,relays:{}}),"identity.getProfile":e=>a({type:"identity.getProfile.result",id:e.id,profile:null}),"identity.getFollows":e=>a({type:"identity.getFollows.result",id:e.id,pubkeys:[]}),"identity.getList":e=>a({type:"identity.getList.result",id:e.id,entries:[]}),"identity.getZaps":e=>a({type:"identity.getZaps.result",id:e.id,zaps:[]}),"identity.getMutes":e=>a({type:"identity.getMutes.result",id:e.id,pubkeys:[]}),"identity.getBlocked":e=>a({type:"identity.getBlocked.result",id:e.id,pubkeys:[]}),"identity.getBadges":e=>a({type:"identity.getBadges.result",id:e.id,badges:[]}),"storage.get":e=>a({type:"storage.get.result",id:e.id,value:null}),"storage.set":e=>a({type:"storage.set.result",id:e.id}),"storage.remove":e=>a({type:"storage.remove.result",id:e.id}),"storage.keys":e=>a({type:"storage.keys.result",id:e.id,keys:[]}),"inc.emit":m,"inc.subscribe":e=>a({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":m,"inc.channel.open":e=>a({type:"inc.channel.open.result",id:e.id,channelId:`chan-${String(e.id)}`,peer:"reference-peer"}),"inc.channel.emit":m,"inc.channel.broadcast":m,"inc.channel.list":e=>a({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":m,"theme.get":e=>a({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":m,"keys.registerAction":e=>a({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":m,"media.session.create":e=>a({type:"media.session.create.result",id:e.id,sessionId:`session-${String(e.id)}`,owner:e.owner}),"media.session.update":m,"media.session.destroy":m,"media.state":m,"media.capabilities":m,"notify.send":e=>a({type:"notify.send.result",id:e.id,notificationId:`notif-${String(e.id)}`}),"notify.dismiss":m,"notify.badge":m,"notify.channel.register":m,"notify.permission.request":e=>a({type:"notify.permission.result",id:e.id,granted:!0}),"config.registerSchema":e=>a({type:"config.registerSchema.result",id:e.id,ok:!0}),"config.get":e=>a({type:"config.values",id:e.id,values:{}}),"config.subscribe":()=>a({type:"config.values",values:{}}),"config.unsubscribe":m,"config.openSettings":m,"resource.bytes":e=>a({type:"resource.bytes.result",id:e.id,blob:new Blob([]),mime:"application/octet-stream"}),"resource.cancel":m,"cvm.discover":e=>a({type:"cvm.discover.result",id:e.id,servers:[]}),"cvm.request":e=>a({type:"cvm.request.result",id:e.id,message:{}}),"cvm.close":e=>a({type:"cvm.close.result",id:e.id}),"outbox.query":e=>a({type:"outbox.query.result",id:e.id,events:[],relays:{}}),"outbox.subscribe":e=>a({type:"outbox.eose",subId:e.subId}),"outbox.close":m,"outbox.publish":e=>a({type:"outbox.publish.result",id:e.id,ok:!0}),"outbox.resolveRelays":e=>a({type:"outbox.resolveRelays.result",id:e.id,plan:{}}),"upload.upload":e=>a({type:"upload.upload.result",id:e.id,result:{url:ae}}),"upload.status":e=>a({type:"upload.status.result",id:e.id,status:{}}),"intent.invoke":e=>a({type:"intent.invoke.result",id:e.id,result:{}}),"intent.available":e=>a({type:"intent.available.result",id:e.id,availability:{}}),"intent.handlers":e=>a({type:"intent.handlers.result",id:e.id,handlers:[]})};function de(e={}){const t=e.now??(()=>Date.now()),i={naps:e.capabilities?.naps??[...T],sandbox:e.capabilities?.sandbox??[]},n=[];function s(r){const d=r&&typeof r=="object"&&typeof r.type=="string"?r.type:void 0;if(d==="shell.ready")return[{type:"shell.init",capabilities:{naps:i.naps,sandbox:i.sandbox}}];if(n.push({envelope:r,verdict:ie(r),timestamp:t()}),!d)return[];const u=le[d];return u?u(r):[]}return{capabilities:i,get records(){return n},handle:s,reset(){n.length=0}}}function ce(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 C(e){return new Promise(t=>setTimeout(t,e))}async function L(e,t,i,n){const s=de(e?{capabilities:e}:void 0),r=i.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 u=new Promise(h=>{d=h}),l=h=>{if(h.source!==r.contentWindow)return;const F=s.handle(h.data),U=h.source;for(const G of F)U?.postMessage(G,"*");const x=h.data;x&&typeof x=="object"&&x.type==="shell.ready"&&d()};n.addEventListener("message",l),(i.body??i.documentElement).appendChild(r),r.setAttribute("src",t.url);let y=!1,b=null;const p=Symbol("timeout");await Promise.race([u.then(()=>"ready"),C(t.readyTimeoutMs).then(()=>p)])===p?b=`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 C(t.settleMs)),n.removeEventListener("message",l);try{r.remove()}catch{}return{installedGlobal:y,bootError:b,emitted:[...s.records]}}async function ue(e){const t=e.document??document,i=e.window??window,n={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},s=await L(void 0,n,t,i);let r=null;{const d=await L({naps:[],sandbox:[]},n,t,i);r={bootError:d.bootError,emitted:d.emitted}}return{installedGlobal:s.installedGlobal,bootError:s.bootError,emitted:s.emitted,degraded:r}}var pe=e=>({status:"pass",detail:e}),fe=(e,t)=>({status:"fail",detail:e,diagnostics:t}),me=e=>({status:"skip",detail:e}),c={pass:pe,fail:fe,skip:me};function B(e,t){return N(e).errors.filter(i=>t.includes(i.code))}function H(e,t){return new RegExp(`name\\s*=\\s*["']${t}["']`,"i").test(e)}function S(e,t,i,n){return{id:e,area:"manifest",severity:"error",title:t,run:s=>{if(n&&!H(s.manifestHtml,n))return c.skip(`No ${n} declared`);const r=B(s.manifestHtml,i);return r.length?c.fail(r[0].message,r):c.pass()}}}var ge=[S("manifest/napplet-type","Declares a valid napplet-type",["missing-napplet-type","invalid-napplet-type"]),S("manifest/declared-naps","napplet-requires lists only real NAP domains",["unknown-required-nap"],"napplet-requires"),S("manifest/config-schema","Config schema is a draft-07 core subset",["invalid-config-schema"],"napplet-config-schema"),S("manifest/connect-origins","Declared connect origins are valid",["invalid-connect-origin"],"napplet-connect-requires"),{id:"manifest/no-inline-scripts",area:"manifest",severity:"error",title:"No inline <script> (incompatible with script-src 'self')",run:e=>{const t=B(e.manifestHtml,["inline-script"]);return t.length?c.fail(`${t.length} inline script(s) found`,t):c.pass()}},{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:"Installs window.napplet",run:e=>e.installedGlobal?c.pass():c.fail("window.napplet was not present after load")},{id:"boot/no-boot-error",area:"boot",severity:"error",title:"Boots without an uncaught error",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(n=>!n.verdict.ok);if(t.length===0)return c.pass(`${e.emitted.length} envelope(s) valid`);const i=t.map(n=>({envelope:n.envelope,errors:n.verdict.errors}));return c.fail(`${t.length}/${e.emitted.length} emitted envelope(s) invalid`,i)}},{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(!H(e.manifestHtml,"napplet-requires"))return c.skip("No napplet-requires to compare against");const t=new Set(N(e.manifestHtml).requires.map(s=>s.startsWith("nap:")?s.slice(4):s)),n=[...new Set(e.emitted.map(s=>s.verdict.domain).filter(s=>typeof s=="string"))].filter(s=>!t.has(s));return n.length?c.fail(`Emitted undeclared NAP domain(s): ${n.join(", ")}`,n):c.pass()}},{id:"degrade/supports-false",area:"degradation",severity:"error",title:"Does not crash when shell.supports() is 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 ye(e,t={}){const i=t.now??(()=>Date.now()),n=t.checks??ge,s=i(),r=n.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}}),d={total:r.length,passed:r.filter(l=>l.status==="pass").length,failed:r.filter(l=>l.status==="fail").length,skipped:r.filter(l=>l.status==="skip").length,errors:r.filter(l=>l.status==="fail"&&l.severity==="error").length,warnings:r.filter(l=>l.status==="fail"&&l.severity==="warning").length};return{napplet:N(e.manifestHtml).nappletType,startedAt:s,finishedAt:i(),checks:r,summary:d,ok:d.errors===0}}const be=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,he="/__conformance__/events",_=new URLSearchParams(location.search),D=_.get("live")==="1";function v(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const w=document.getElementById("app");function ve(){return w.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:w.querySelector("#url"),runButton:w.querySelector("#run"),rerunButton:w.querySelector("#rerun"),liveBadge:w.querySelector("#live-badge")??document.createElement("span"),status:w.querySelector("#status"),output:w.querySelector("#output")}}const g=ve();let E="",A=0;function I(e,t="info"){g.status.textContent=e,g.status.dataset.kind=t}function P(e){return String(e).padStart(2,"0")}function we(e){const t=new Date(e);return`${P(t.getHours())}:${P(t.getMinutes())}:${P(t.getSeconds())}`}async function $e(e){try{const i=await(await fetch(e,{mode:"cors"})).text();return{html:i,forbidden:be.test(i)?["window.nostr"]:[],fetched:!0}}catch{return{html:"",forbidden:[],fetched:!1}}}function Ee(e){const t=e.ok?"conformant":"non-conformant",n=[...new Set(e.checks.map(d=>d.area))].map(d=>{const u=e.checks.filter(l=>l.area===d).map(l=>{const y=l.severity==="warning"&&l.status==="fail"?' <span class="tag warn">warning</span>':"",b=l.detail?`<span class="detail">${v(l.detail)}</span>`:"";return`<li class="check ${l.status}"><span class="badge ${l.status}">${l.status}</span><span class="id">${v(l.id)}</span>${y}${b}</li>`}).join("");return`<div class="area"><h3>${v(d)}</h3><ul>${u}</ul></div>`}).join(""),s=e.summary,r=A>1?`<span class="run-stamp">run #${A} · ${we(e.finishedAt)}</span>`:"";return`
14
+ <div class="verdict ${t}">
15
+ <span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
16
+ <span class="counts">${s.passed} passed · ${s.failed} failed · ${s.skipped} skipped${s.warnings?` · ${s.warnings} warning(s)`:""}</span>
17
+ ${r}
18
+ </div>
19
+ <div class="checks">${n}</div>
20
+ `}function ke(e){return e.length===0?'<p class="empty">No envelopes were emitted.</p>':`<ul class="envlog">${e.map(i=>{const n=i.verdict.ok?"ok":"bad",s=i.verdict.ok?"":`<div class="errs">${i.verdict.errors.map(r=>v(`${r.code}: ${r.message}`)).join("<br>")}</div>`;return`<li class="env ${n}"><code>${v(JSON.stringify(i.envelope))}</code>${s}</li>`}).join("")}</ul>`}function Se(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 i=N(e),n=(s,r)=>`<tr><th>${v(s)}</th><td>${r?v(String(r)):"<em>—</em>"}</td></tr>`;return`
21
+ <table class="manifest">
22
+ ${n("napplet-type",i.nappletType)}
23
+ ${n("requires",i.requires.join(", "))}
24
+ ${n("connect origins",i.connectOrigins.join(", "))}
25
+ </table>
26
+ ${i.errors.length?`<div class="errs">${i.errors.map(s=>v(`${s.code}: ${s.message}`)).join("<br>")}</div>`:""}
27
+ `}function Ie(e,t,i,n){g.output.innerHTML=`
28
+ ${Ee(e)}
29
+ <div class="panels">
30
+ <section class="panel"><h2>Emitted envelopes (${t.length})</h2>${ke(t)}</section>
31
+ <section class="panel"><h2>Manifest</h2>${Se(i,n)}</section>
32
+ </div>
33
+ `}async function O(e){E=e,g.runButton.disabled=!0,g.rerunButton.disabled=!0;try{I(`Fetching manifest for ${e} …`);const{html:t,forbidden:i,fetched:n}=await $e(e);I("Booting napplet in a sandboxed iframe …");const s=await ue({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),r=ce({manifestHtml:t,boot:s,forbiddenGlobals:i}),d=ye(r);A+=1,I(d.ok?"Done — conformant.":"Done — non-conformant.",d.ok?"info":"error"),Ie(d,s.emitted,t,n),g.rerunButton.hidden=!1}catch(t){I(`Error: ${t instanceof Error?t.message:String(t)}`,"error")}finally{g.runButton.disabled=!1,g.rerunButton.disabled=!1}}function Ne(){if(!D||typeof EventSource>"u")return;let e=!1;const t=new EventSource(he);t.addEventListener("rerun",()=>{!E||e||(e=!0,setTimeout(()=>{e=!1,O(E)},50))}),t.addEventListener("open",()=>{g.liveBadge&&(g.liveBadge.dataset.state="connected")}),t.addEventListener("error",()=>{g.liveBadge&&(g.liveBadge.dataset.state="disconnected")})}g.runButton.parentElement.addEventListener("submit",e=>{e.preventDefault();const t=g.urlInput.value.trim();if(t){const i=new URL(location.href);i.searchParams.set("url",t),history.replaceState(null,"",i),O(t)}});g.rerunButton.addEventListener("click",()=>{E&&O(E)});Ne();const q=_.get("url");q&&(g.urlInput.value=q,O(q));
@@ -5,7 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>napplet conformance runtime</title>
7
7
  <meta name="description" content="Load a napplet and run NAP protocol conformance checks live in your browser." />
8
- <script type="module" crossorigin src="/assets/index-iVwcmekY.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-RadcQqJ3.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.1.0",
3
+ "version": "0.1.1",
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,7 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "playwright": "^1.59.1",
18
- "@napplet/conformance": "^0.1.0"
18
+ "@napplet/conformance": "^0.1.1"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^22.15.0",
@@ -1,34 +0,0 @@
1
- (function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const n of document.querySelectorAll('link[rel="modulepreload"]'))i(n);new MutationObserver(n=>{for(const r of n)if(r.type==="childList")for(const a of r.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&i(a)}).observe(document,{childList:!0,subtree:!0});function s(n){const r={};return n.integrity&&(r.integrity=n.integrity),n.referrerPolicy&&(r.referrerPolicy=n.referrerPolicy),n.crossOrigin==="use-credentials"?r.credentials="include":n.crossOrigin==="anonymous"?r.credentials="omit":r.credentials="same-origin",r}function i(n){if(n.ep)return;n.ep=!0;const r=s(n);fetch(n.href,r)}})();var j=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","connect","class","cvm","outbox","upload","intent"],f="[@napplet/nap/connect]",K=new Set(["https","wss","http","ws"]),W={https:"443",wss:"443",http:"80",ws:"80"};function z(e){const t=e.indexOf("://");if(t<0)throw new Error(`${f} origin missing scheme separator '://': ${e}`);const s=e.slice(0,t);if(s!==s.toLowerCase())throw new Error(`${f} scheme must be lowercase: ${e}`);if(!K.has(s))throw new Error(`${f} scheme must be one of https|wss|http|ws: ${e}`);return{scheme:s,rest:e.slice(t+3)}}function V(e,t){if(e.includes("/"))throw new Error(`${f} origin MUST NOT contain a path: ${t}`);if(e.includes("?"))throw new Error(`${f} origin MUST NOT contain a query: ${t}`);if(e.includes("#"))throw new Error(`${f} origin MUST NOT contain a fragment: ${t}`);if(e.includes("*"))throw new Error(`${f} wildcard origins are not permitted: ${t}`);if(e.startsWith("["))throw new Error(`${f} IPv6 literal origins are out of v1 scope: ${t}`)}function J(e,t,s){let i=t;const n=t.lastIndexOf(":");if(n>=0){i=t.slice(0,n);const r=t.slice(n+1);if(!/^[0-9]+$/.test(r))throw new Error(`${f} port must be decimal digits: ${s}`);const a=Number(r);if(a<1||a>65535)throw new Error(`${f} port out of range 1-65535: ${s}`);if(W[e]===r)throw new Error(`${f} default port ${r} MUST be omitted for scheme ${e}: ${s}`)}return Z(i,s),i}function Z(e,t){if(e.includes(":"))throw new Error(`${f} IPv6 literal origins are out of v1 scope: ${t}`);if(e.length===0)throw new Error(`${f} host is empty: ${t}`);if(e!==e.toLowerCase())throw new Error(`${f} host MUST be lowercase: ${t}`);if(!/^[\x00-\x7F]+$/.test(e))throw new Error(`${f} non-ASCII host MUST be Punycode-encoded (xn--...) before calling: ${t}`)}function Y(e,t){if(!/^[0-9]+(\.[0-9]+){3}$/.test(e))return!1;for(const s of e.split(".")){if(s.length>1&&s.startsWith("0"))throw new Error(`${f} IPv4 octet MUST NOT have leading zeros: ${t}`);const i=Number(s);if(!Number.isInteger(i)||i<0||i>255)throw new Error(`${f} IPv4 octet out of range 0-255: ${t}`)}return!0}function X(e,t){for(const s of e.split(".")){if(s.length===0||s.length>63)throw new Error(`${f} invalid host label length: ${t}`);if(!/^[a-z0-9-]+$/.test(s))throw new Error(`${f} host label contains invalid characters: ${t}`);if(s.startsWith("-")||s.endsWith("-"))throw new Error(`${f} host label MUST NOT start or end with hyphen: ${t}`)}}function Q(e){if(typeof e!="string"||e.length===0)throw new Error(`${f} origin must be a non-empty string`);const{scheme:t,rest:s}=z(e);V(s,e);const i=J(t,s,e);return Y(i,e)||X(i,e),e}var o={id:"string"},ee={"relay.subscribe":{dir:"out",fields:{...o,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...o,subId:"string"}},"relay.publish":{dir:"out",fields:{...o,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...o,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...o,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:{...o}},"identity.getRelays":{dir:"out",fields:{...o}},"identity.getProfile":{dir:"out",fields:{...o}},"identity.getFollows":{dir:"out",fields:{...o}},"identity.getList":{dir:"out",fields:{...o,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...o}},"identity.getMutes":{dir:"out",fields:{...o}},"identity.getBlocked":{dir:"out",fields:{...o}},"identity.getBadges":{dir:"out",fields:{...o}},"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:{...o,key:"string"}},"storage.set":{dir:"out",fields:{...o,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...o,key:"string"}},"storage.keys":{dir:"out",fields:{...o}},"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:{...o,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...o,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...o}},"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:{...o}},"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:{...o,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:{...o,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:{...o,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:{...o}},"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:{...o,schema:"object"}},"config.get":{dir:"out",fields:{...o}},"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:{...o,url:"string"}},"resource.cancel":{dir:"out",fields:{...o}},"resource.bytes.result":{dir:"in"},"resource.bytes.error":{dir:"in"},"class.assigned":{dir:"in"},"cvm.discover":{dir:"out",fields:{...o}},"cvm.request":{dir:"out",fields:{...o,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...o,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:{...o,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...o,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...o,subId:"string"}},"outbox.publish":{dir:"out",fields:{...o,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...o,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:{...o,request:"object"}},"upload.status":{dir:"out",fields:{...o,uploadId:"string"}},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...o,request:"object"}},"intent.available":{dir:"out",fields:{...o,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...o}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"}};function H(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 te(e,t){return t==="present"?e!=null:H(e)===t}function ie(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,i=s.type;if(typeof i!="string")return{ok:!1,errors:[{code:"missing-type",message:"Envelope is missing a string `type` field"}]};const n=i.indexOf(".");if(n<=0)return{ok:!1,type:i,errors:[{code:"malformed-type",message:`Envelope type "${i}" is not in domain.action form`}]};const r=i.slice(0,n);if(!j.includes(r))return{ok:!1,type:i,domain:r,errors:[{code:"unknown-domain",message:`"${r}" is not a known NAP domain`}]};const a=ee[i];if(!a)return{ok:!1,type:i,domain:r,errors:[{code:"unknown-type",message:`"${i}" is not a known ${r} message type`}]};if(a.dir==="in")return{ok:!1,type:i,domain:r,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${i}" is a shell→napplet message; a napplet must not emit it`}]};for(const[p,d]of Object.entries(a.fields??{})){if(!(p in s)||s[p]===void 0){t.push({code:"missing-field",message:`Required field "${p}" is missing`,field:p});continue}te(s[p],d)||t.push({code:"wrong-type",message:`Field "${p}" should be ${d} but is ${H(s[p])}`,field:p})}return{ok:t.length===0,type:i,domain:r,direction:"out",errors:t}}var C=/^[a-z0-9][a-z0-9._:-]*$/,se=/^[0-9a-f]{64}$/,re=new Set(["application/json","application/ld+json","importmap","speculationrules"]);function $(e,t){const s=/<meta\b[^>]*>/gi;let i;for(;(i=s.exec(e))!==null;){const n=i[0],r=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(n);if((r?.[1]??r?.[2]??"").trim()!==t)continue;const p=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(n);return(p?.[1]??p?.[2]??"").trim()}}function ne(e){const t=e.replace(/<!--[\s\S]*?-->/g,""),s=/<script\b([\s\S]*?)>/gi,i=[];let n;for(;(n=s.exec(t))!==null;){const r=n[0],a=n[1]??"",p=/\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(a);if(p){if((p[1]??p[2]??"").trim().length>0)continue;i.push(r);continue}const d=/\btype\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(a),y=(d?.[1]??d?.[2]??"").trim().toLowerCase();y&&re.has(y)||i.push(r)}return i}function T(e){if(Array.isArray(e))return e.some(T);if(e&&typeof e=="object"){const t=e;return"pattern"in t?!0:Object.values(t).some(T)}return!1}function N(e,t={}){const s=t.requireAggregateHash??!0,i=[],n=[],r=$(e,"napplet-type");r?C.test(r)||i.push({code:"invalid-napplet-type",message:`napplet-type "${r}" must match ${C}`}):i.push({code:"missing-napplet-type",message:'No <meta name="napplet-type"> declared'});const a=$(e,"napplet-aggregate-hash");a?se.test(a)||i.push({code:"invalid-aggregate-hash",message:`napplet-aggregate-hash "${a}" is not a 64-char lowercase hex digest`}):s&&i.push({code:"missing-aggregate-hash",message:'No <meta name="napplet-aggregate-hash"> — the build plugin injects it; build before testing'});const p=$(e,"napplet-requires"),d=p?p.split(",").map(c=>c.trim()).filter(Boolean):[];for(const c of d){const v=c.startsWith("nap:")?c.slice(4):c;j.includes(v)||i.push({code:"unknown-required-nap",message:`napplet-requires lists "${c}" which is not a known NAP domain`})}const y=$(e,"napplet-config-schema");if(y){let c;try{c=JSON.parse(y)}catch{c=void 0,i.push({code:"invalid-config-schema",message:"napplet-config-schema is not valid JSON"})}c!==void 0&&(typeof c!="object"||c===null||Array.isArray(c)?i.push({code:"invalid-config-schema",message:"napplet-config-schema must be a JSON Schema object"}):T(c)&&i.push({code:"invalid-config-schema",message:"napplet-config-schema uses the `pattern` keyword, which the draft-07 core subset excludes (CVE-2025-69873)"}))}const h=$(e,"napplet-connect-requires"),S=h?h.split(/\s+/).map(c=>c.trim()).filter(Boolean):[],q=[];for(const c of S)try{q.push(Q(c))}catch(v){const x=v instanceof Error?v.message:String(v);i.push({code:"invalid-connect-origin",message:`connect origin "${c}" is invalid: ${x}`})}for(const c of ne(e))i.push({code:"inline-script",message:`Inline <script> is forbidden under script-src 'self': ${c}`});return{ok:i.length===0,nappletType:r,aggregateHash:a,requires:d,connectOrigins:q,errors:i,warnings:n}}var oe="f".repeat(64),ae="https://reference.invalid/blob",l=e=>[e],g=()=>[],le={"relay.subscribe":e=>l({type:"relay.eose",subId:e.subId}),"relay.close":g,"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:oe}),"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":g,"inc.subscribe":e=>l({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":g,"inc.channel.open":e=>l({type:"inc.channel.open.result",id:e.id,channelId:`chan-${String(e.id)}`,peer:"reference-peer"}),"inc.channel.emit":g,"inc.channel.broadcast":g,"inc.channel.list":e=>l({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":g,"theme.get":e=>l({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":g,"keys.registerAction":e=>l({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":g,"media.session.create":e=>l({type:"media.session.create.result",id:e.id,sessionId:`session-${String(e.id)}`,owner:e.owner}),"media.session.update":g,"media.session.destroy":g,"media.state":g,"media.capabilities":g,"notify.send":e=>l({type:"notify.send.result",id:e.id,notificationId:`notif-${String(e.id)}`}),"notify.dismiss":g,"notify.badge":g,"notify.channel.register":g,"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":g,"config.openSettings":g,"resource.bytes":e=>l({type:"resource.bytes.result",id:e.id,blob:new Blob([]),mime:"application/octet-stream"}),"resource.cancel":g,"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":g,"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:ae}}),"upload.status":e=>l({type:"upload.status.result",id:e.id,status:{}}),"intent.invoke":e=>l({type:"intent.invoke.result",id:e.id,result:{}}),"intent.available":e=>l({type:"intent.available.result",id:e.id,availability:{}}),"intent.handlers":e=>l({type:"intent.handlers.result",id:e.id,handlers:[]})};function de(e={}){const t=e.now??(()=>Date.now()),s={naps:e.capabilities?.naps??[...j],sandbox:e.capabilities?.sandbox??[]},i=[];function n(r){const a=r&&typeof r=="object"&&typeof r.type=="string"?r.type:void 0;if(a==="shell.ready")return[{type:"shell.init",capabilities:{naps:s.naps,sandbox:s.sandbox}}];if(i.push({envelope:r,verdict:ie(r),timestamp:t()}),!a)return[];const p=le[a];return p?p(r):[]}return{capabilities:s,get records(){return i},handle:n,reset(){i.length=0}}}function ce(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 L(e){return new Promise(t=>setTimeout(t,e))}async function B(e,t,s,i){const n=de(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 a;const p=new Promise(c=>{a=c}),d=c=>{if(c.source!==r.contentWindow)return;const v=n.handle(c.data),x=c.source;for(const U of v)x?.postMessage(U,"*");const A=c.data;A&&typeof A=="object"&&A.type==="shell.ready"&&a()};i.addEventListener("message",d),(s.body??s.documentElement).appendChild(r),r.setAttribute("src",t.url);let y=!1,h=null;const S=Symbol("timeout");await Promise.race([p.then(()=>"ready"),L(t.readyTimeoutMs).then(()=>S)])===S?h=`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 L(t.settleMs)),i.removeEventListener("message",d);try{r.remove()}catch{}return{installedGlobal:y,bootError:h,emitted:[...n.records]}}async function ue(e){const t=e.document??document,s=e.window??window,i={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},n=await B(void 0,i,t,s);let r=null;{const a=await B({naps:[],sandbox:[]},i,t,s);r={bootError:a.bootError,emitted:a.emitted}}return{installedGlobal:n.installedGlobal,bootError:n.bootError,emitted:n.emitted,degraded:r}}var pe=e=>({status:"pass",detail:e}),fe=(e,t)=>({status:"fail",detail:e,diagnostics:t}),ge=e=>({status:"skip",detail:e}),u={pass:pe,fail:fe,skip:ge};function _(e,t){return N(e).errors.filter(s=>t.includes(s.code))}function D(e,t){return new RegExp(`name\\s*=\\s*["']${t}["']`,"i").test(e)}function E(e,t,s,i){return{id:e,area:"manifest",severity:"error",title:t,run:n=>{if(i&&!D(n.manifestHtml,i))return u.skip(`No ${i} declared`);const r=_(n.manifestHtml,s);return r.length?u.fail(r[0].message,r):u.pass()}}}var me=[E("manifest/napplet-type","Declares a valid napplet-type",["missing-napplet-type","invalid-napplet-type"]),E("manifest/aggregate-hash","Build injected a valid aggregate hash",["missing-aggregate-hash","invalid-aggregate-hash"]),E("manifest/declared-naps","napplet-requires lists only real NAP domains",["unknown-required-nap"],"napplet-requires"),E("manifest/config-schema","Config schema is a draft-07 core subset",["invalid-config-schema"],"napplet-config-schema"),E("manifest/connect-origins","Declared connect origins are valid",["invalid-connect-origin"],"napplet-connect-requires"),{id:"manifest/no-inline-scripts",area:"manifest",severity:"error",title:"No inline <script> (incompatible with script-src 'self')",run:e=>{const t=_(e.manifestHtml,["inline-script"]);return t.length?u.fail(`${t.length} inline script(s) found`,t):u.pass()}},{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:"Installs window.napplet",run:e=>e.installedGlobal?u.pass():u.fail("window.napplet was not present after 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(i=>!i.verdict.ok);if(t.length===0)return u.pass(`${e.emitted.length} envelope(s) valid`);const s=t.map(i=>({envelope:i.envelope,errors:i.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(!D(e.manifestHtml,"napplet-requires"))return u.skip("No napplet-requires to compare against");const t=new Set(N(e.manifestHtml).requires.map(n=>n.startsWith("nap:")?n.slice(4):n)),i=[...new Set(e.emitted.map(n=>n.verdict.domain).filter(n=>typeof n=="string"))].filter(n=>!t.has(n));return i.length?u.fail(`Emitted undeclared NAP domain(s): ${i.join(", ")}`,i):u.pass()}},{id:"degrade/supports-false",area:"degradation",severity:"error",title:"Does not crash when shell.supports() is 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 ye(e,t={}){const s=t.now??(()=>Date.now()),i=t.checks??me,n=s(),r=i.map(d=>{const y=d.run(e);return{id:d.id,area:d.area,severity:d.severity,title:d.title,status:y.status,detail:y.detail,diagnostics:y.diagnostics}}),a={total:r.length,passed:r.filter(d=>d.status==="pass").length,failed:r.filter(d=>d.status==="fail").length,skipped:r.filter(d=>d.status==="skip").length,errors:r.filter(d=>d.status==="fail"&&d.severity==="error").length,warnings:r.filter(d=>d.status==="fail"&&d.severity==="warning").length};return{napplet:N(e.manifestHtml).nappletType,startedAt:n,finishedAt:s(),checks:r,summary:a,ok:a.errors===0}}const he=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,be="/__conformance__/events",F=new URLSearchParams(location.search),G=F.get("live")==="1";function b(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const w=document.getElementById("app");function ve(){return w.innerHTML=`
2
- <header class="masthead">
3
- <h1>napplet conformance ${G?'<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:w.querySelector("#url"),runButton:w.querySelector("#run"),rerunButton:w.querySelector("#rerun"),liveBadge:w.querySelector("#live-badge")??document.createElement("span"),status:w.querySelector("#status"),output:w.querySelector("#output")}}const m=ve();let k="",M=0;function I(e,t="info"){m.status.textContent=e,m.status.dataset.kind=t}function R(e){return String(e).padStart(2,"0")}function we(e){const t=new Date(e);return`${R(t.getHours())}:${R(t.getMinutes())}:${R(t.getSeconds())}`}async function $e(e){try{const s=await(await fetch(e,{mode:"cors"})).text();return{html:s,forbidden:he.test(s)?["window.nostr"]:[],fetched:!0}}catch{return{html:"",forbidden:[],fetched:!1}}}function Ee(e){const t=e.ok?"conformant":"non-conformant",i=[...new Set(e.checks.map(a=>a.area))].map(a=>{const p=e.checks.filter(d=>d.area===a).map(d=>{const y=d.severity==="warning"&&d.status==="fail"?' <span class="tag warn">warning</span>':"",h=d.detail?`<span class="detail">${b(d.detail)}</span>`:"";return`<li class="check ${d.status}"><span class="badge ${d.status}">${d.status}</span><span class="id">${b(d.id)}</span>${y}${h}</li>`}).join("");return`<div class="area"><h3>${b(a)}</h3><ul>${p}</ul></div>`}).join(""),n=e.summary,r=M>1?`<span class="run-stamp">run #${M} · ${we(e.finishedAt)}</span>`:"";return`
14
- <div class="verdict ${t}">
15
- <span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
16
- <span class="counts">${n.passed} passed · ${n.failed} failed · ${n.skipped} skipped${n.warnings?` · ${n.warnings} warning(s)`:""}</span>
17
- ${r}
18
- </div>
19
- <div class="checks">${i}</div>
20
- `}function ke(e){return e.length===0?'<p class="empty">No envelopes were emitted.</p>':`<ul class="envlog">${e.map(s=>{const i=s.verdict.ok?"ok":"bad",n=s.verdict.ok?"":`<div class="errs">${s.verdict.errors.map(r=>b(`${r.code}: ${r.message}`)).join("<br>")}</div>`;return`<li class="env ${i}"><code>${b(JSON.stringify(s.envelope))}</code>${n}</li>`}).join("")}</ul>`}function Se(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=N(e),i=(n,r)=>`<tr><th>${b(n)}</th><td>${r?b(String(r)):"<em>—</em>"}</td></tr>`;return`
21
- <table class="manifest">
22
- ${i("napplet-type",s.nappletType)}
23
- ${i("aggregate-hash",s.aggregateHash)}
24
- ${i("requires",s.requires.join(", "))}
25
- ${i("connect origins",s.connectOrigins.join(", "))}
26
- </table>
27
- ${s.errors.length?`<div class="errs">${s.errors.map(n=>b(`${n.code}: ${n.message}`)).join("<br>")}</div>`:""}
28
- `}function Ie(e,t,s,i){m.output.innerHTML=`
29
- ${Ee(e)}
30
- <div class="panels">
31
- <section class="panel"><h2>Emitted envelopes (${t.length})</h2>${ke(t)}</section>
32
- <section class="panel"><h2>Manifest</h2>${Se(s,i)}</section>
33
- </div>
34
- `}async function O(e){k=e,m.runButton.disabled=!0,m.rerunButton.disabled=!0;try{I(`Fetching manifest for ${e} …`);const{html:t,forbidden:s,fetched:i}=await $e(e);I("Booting napplet in a sandboxed iframe …");const n=await ue({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),r=ce({manifestHtml:t,boot:n,forbiddenGlobals:s}),a=ye(r);M+=1,I(a.ok?"Done — conformant.":"Done — non-conformant.",a.ok?"info":"error"),Ie(a,n.emitted,t,i),m.rerunButton.hidden=!1}catch(t){I(`Error: ${t instanceof Error?t.message:String(t)}`,"error")}finally{m.runButton.disabled=!1,m.rerunButton.disabled=!1}}function Ne(){if(!G||typeof EventSource>"u")return;let e=!1;const t=new EventSource(be);t.addEventListener("rerun",()=>{!k||e||(e=!0,setTimeout(()=>{e=!1,O(k)},50))}),t.addEventListener("open",()=>{m.liveBadge&&(m.liveBadge.dataset.state="connected")}),t.addEventListener("error",()=>{m.liveBadge&&(m.liveBadge.dataset.state="disconnected")})}m.runButton.parentElement.addEventListener("submit",e=>{e.preventDefault();const t=m.urlInput.value.trim();if(t){const s=new URL(location.href);s.searchParams.set("url",t),history.replaceState(null,"",s),O(t)}});m.rerunButton.addEventListener("click",()=>{k&&O(k)});Ne();const P=F.get("url");P&&(m.urlInput.value=P,O(P));