@napplet/conformance-cli 0.2.14 → 0.2.15
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.
- package/dist/host-bundle.global.js +92 -85
- package/dist/ui/assets/index-j_MRjiMC.js +113 -0
- package/dist/ui/index.html +1 -1
- package/package.json +3 -3
- package/dist/ui/assets/index-DZ1po6iW.js +0 -107
|
@@ -325,76 +325,92 @@ var NappletConformanceHostBundle = (() => {
|
|
|
325
325
|
}
|
|
326
326
|
return { ok: errors.length === 0, type, domain, direction: "out", errors };
|
|
327
327
|
}
|
|
328
|
-
var
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
328
|
+
var NAPPLET_KIND_SNAPSHOT = 5129;
|
|
329
|
+
var NAPPLET_KIND_ROOT = 15129;
|
|
330
|
+
var NAPPLET_KIND_NAMED = 35129;
|
|
331
|
+
var NAPPLET_MANIFEST_KINDS = [
|
|
332
|
+
NAPPLET_KIND_SNAPSHOT,
|
|
333
|
+
NAPPLET_KIND_ROOT,
|
|
334
|
+
NAPPLET_KIND_NAMED
|
|
335
|
+
];
|
|
336
|
+
function firstTag(event, name) {
|
|
337
|
+
return event.tags.find((tag) => tag[0] === name);
|
|
338
338
|
}
|
|
339
|
-
function
|
|
340
|
-
|
|
341
|
-
let tag;
|
|
342
|
-
while ((tag = tagRe.exec(html)) !== null) {
|
|
343
|
-
const block = tag[0];
|
|
344
|
-
const nameMatch = /\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(block);
|
|
345
|
-
const metaName = (nameMatch?.[1] ?? nameMatch?.[2] ?? "").trim();
|
|
346
|
-
if (metaName !== name) continue;
|
|
347
|
-
const contentMatch = /\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(block);
|
|
348
|
-
return decodeHtmlEntities((contentMatch?.[1] ?? contentMatch?.[2] ?? "").trim());
|
|
349
|
-
}
|
|
350
|
-
return void 0;
|
|
339
|
+
function isNappletKind(kind) {
|
|
340
|
+
return NAPPLET_MANIFEST_KINDS.includes(kind);
|
|
351
341
|
}
|
|
352
|
-
function
|
|
353
|
-
|
|
354
|
-
if (node && typeof node === "object") {
|
|
355
|
-
const record = node;
|
|
356
|
-
if ("pattern" in record) return true;
|
|
357
|
-
return Object.values(record).some(usesPatternKeyword);
|
|
358
|
-
}
|
|
359
|
-
return false;
|
|
342
|
+
function isSha256Hex(value) {
|
|
343
|
+
return typeof value === "string" && /^[0-9a-f]{64}$/i.test(value);
|
|
360
344
|
}
|
|
361
|
-
function
|
|
345
|
+
function manifestDTag(event) {
|
|
346
|
+
const d = firstTag(event, "d")?.[1]?.trim();
|
|
347
|
+
return d || void 0;
|
|
348
|
+
}
|
|
349
|
+
function manifestRequires(event) {
|
|
350
|
+
return event.tags.filter((tag) => tag[0] === "requires").map((tag) => tag[1]?.trim() ?? "").filter(Boolean);
|
|
351
|
+
}
|
|
352
|
+
function validateManifestEvent(event) {
|
|
362
353
|
const errors = [];
|
|
363
354
|
const warnings = [];
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
355
|
+
if (!event) {
|
|
356
|
+
return {
|
|
357
|
+
ok: false,
|
|
358
|
+
requires: [],
|
|
359
|
+
errors: [{ code: "missing-manifest-event", message: "No NIP-5D manifest event was resolved" }],
|
|
360
|
+
warnings
|
|
361
|
+
};
|
|
369
362
|
}
|
|
370
|
-
const
|
|
371
|
-
const requires =
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
363
|
+
const dTag = manifestDTag(event);
|
|
364
|
+
const requires = manifestRequires(event);
|
|
365
|
+
if (!isNappletKind(event.kind)) {
|
|
366
|
+
errors.push({
|
|
367
|
+
code: "invalid-napplet-kind",
|
|
368
|
+
message: `Manifest event kind ${event.kind} is not a NIP-5D napplet kind`
|
|
369
|
+
});
|
|
377
370
|
}
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
371
|
+
if (event.kind === NAPPLET_KIND_NAMED && !dTag) {
|
|
372
|
+
errors.push({
|
|
373
|
+
code: "missing-d-tag",
|
|
374
|
+
message: "Named napplet manifest kind 35129 must include a non-empty d tag"
|
|
375
|
+
});
|
|
376
|
+
} else if ((event.kind === NAPPLET_KIND_ROOT || event.kind === NAPPLET_KIND_SNAPSHOT) && dTag) {
|
|
377
|
+
errors.push({
|
|
378
|
+
code: "unexpected-d-tag",
|
|
379
|
+
message: `Napplet manifest kind ${event.kind} must not include a d tag`
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
const indexPath = event.tags.find((tag) => tag[0] === "path" && tag[1] === "/index.html");
|
|
383
|
+
if (!indexPath) {
|
|
384
|
+
errors.push({
|
|
385
|
+
code: "missing-index-html",
|
|
386
|
+
message: "Napplet manifest must include a path tag for /index.html"
|
|
387
|
+
});
|
|
388
|
+
} else if (!isSha256Hex(indexPath[2])) {
|
|
389
|
+
errors.push({
|
|
390
|
+
code: "invalid-index-html-hash",
|
|
391
|
+
message: "The /index.html path tag must carry a 64-character sha256 hash"
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
for (const req of requires) {
|
|
395
|
+
if (req.startsWith("nap:") || req.startsWith("NAP-")) {
|
|
396
|
+
errors.push({
|
|
397
|
+
code: "invalid-required-nap",
|
|
398
|
+
message: `requires tag "${req}" must be a bare NAP domain such as "relay"`
|
|
399
|
+
});
|
|
400
|
+
continue;
|
|
386
401
|
}
|
|
387
|
-
if (
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
}
|
|
402
|
+
if (!NAP_DOMAINS.includes(req)) {
|
|
403
|
+
errors.push({
|
|
404
|
+
code: "unknown-required-nap",
|
|
405
|
+
message: `requires tag "${req}" is not a known NAP domain`
|
|
406
|
+
});
|
|
393
407
|
}
|
|
394
408
|
}
|
|
395
409
|
return {
|
|
396
410
|
ok: errors.length === 0,
|
|
397
|
-
|
|
411
|
+
kind: event.kind,
|
|
412
|
+
dTag,
|
|
413
|
+
nappletType: dTag,
|
|
398
414
|
requires,
|
|
399
415
|
errors,
|
|
400
416
|
warnings
|
|
@@ -819,30 +835,37 @@ var NappletConformanceHostBundle = (() => {
|
|
|
819
835
|
var fail = (detail, diagnostics) => ({ status: "fail", detail, diagnostics });
|
|
820
836
|
var skip = (detail) => ({ status: "skip", detail });
|
|
821
837
|
var result = { pass, fail, skip };
|
|
822
|
-
function manifestErrors(
|
|
823
|
-
return
|
|
838
|
+
function manifestErrors(ctx, codes) {
|
|
839
|
+
return validateManifestEvent(ctx.manifestEvent).errors.filter((e) => codes.includes(e.code));
|
|
824
840
|
}
|
|
825
|
-
function
|
|
826
|
-
return new RegExp(`name\\s*=\\s*["']${name}["']`, "i").test(html);
|
|
827
|
-
}
|
|
828
|
-
function manifestCheck(id, title, codes, skipMeta) {
|
|
841
|
+
function manifestCheck(id, title, codes) {
|
|
829
842
|
return {
|
|
830
843
|
id,
|
|
831
844
|
area: "manifest",
|
|
832
845
|
severity: "error",
|
|
833
846
|
title,
|
|
834
847
|
run: (ctx) => {
|
|
835
|
-
if (
|
|
836
|
-
const errs = manifestErrors(ctx
|
|
848
|
+
if (!ctx.manifestEvent) return result.skip("No NIP-5D manifest event was resolved");
|
|
849
|
+
const errs = manifestErrors(ctx, codes);
|
|
837
850
|
return errs.length ? result.fail(errs[0].message, errs) : result.pass();
|
|
838
851
|
}
|
|
839
852
|
};
|
|
840
853
|
}
|
|
841
854
|
var CHECKS = [
|
|
842
855
|
// ── manifest ───────────────────────────────────────────────────────────────
|
|
843
|
-
manifestCheck("manifest/
|
|
844
|
-
|
|
845
|
-
|
|
856
|
+
manifestCheck("manifest/event-kind", "Resolved event is a NIP-5D napplet manifest", [
|
|
857
|
+
"invalid-napplet-kind",
|
|
858
|
+
"missing-d-tag",
|
|
859
|
+
"unexpected-d-tag"
|
|
860
|
+
]),
|
|
861
|
+
manifestCheck("manifest/index-html", "Manifest declares a hashed /index.html", [
|
|
862
|
+
"missing-index-html",
|
|
863
|
+
"invalid-index-html-hash"
|
|
864
|
+
]),
|
|
865
|
+
manifestCheck("manifest/requires", "requires tags are bare known NAP domains", [
|
|
866
|
+
"invalid-required-nap",
|
|
867
|
+
"unknown-required-nap"
|
|
868
|
+
]),
|
|
846
869
|
// ── boot ─────────────────────────────────────────────────────────────────────
|
|
847
870
|
{
|
|
848
871
|
id: "boot/sandbox-allow-scripts",
|
|
@@ -890,22 +913,6 @@ var NappletConformanceHostBundle = (() => {
|
|
|
890
913
|
return result.fail(`${bad.length}/${ctx.emitted.length} emitted envelope(s) invalid`, diag);
|
|
891
914
|
}
|
|
892
915
|
},
|
|
893
|
-
{
|
|
894
|
-
id: "wire/declared-naps-only",
|
|
895
|
-
area: "wire",
|
|
896
|
-
severity: "warning",
|
|
897
|
-
title: "Only emits NAP domains it declared in napplet-requires",
|
|
898
|
-
run: (ctx) => {
|
|
899
|
-
if (ctx.emitted.length === 0) return result.skip("Napplet emitted no envelopes");
|
|
900
|
-
if (!hasMeta(ctx.manifestHtml, "napplet-requires")) return result.skip("No napplet-requires to compare against");
|
|
901
|
-
const declared = new Set(validateManifest(ctx.manifestHtml).requires.map((r) => r.startsWith("nap:") ? r.slice(4) : r));
|
|
902
|
-
const used = new Set(
|
|
903
|
-
ctx.emitted.map((r) => r.verdict.domain).filter((d) => typeof d === "string")
|
|
904
|
-
);
|
|
905
|
-
const undeclared = [...used].filter((d) => !declared.has(d));
|
|
906
|
-
return undeclared.length ? result.fail(`Emitted undeclared NAP domain(s): ${undeclared.join(", ")}`, undeclared) : result.pass();
|
|
907
|
-
}
|
|
908
|
-
},
|
|
909
916
|
// ── degradation ───────────────────────────────────────────────────────────────
|
|
910
917
|
{
|
|
911
918
|
id: "degrade/domain-absence",
|
|
@@ -0,0 +1,113 @@
|
|
|
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"]'))n(i);new MutationObserver(i=>{for(const s of i)if(s.type==="childList")for(const o of s.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&n(o)}).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 n(i){if(i.ep)return;i.ep=!0;const s=r(i);fetch(i.href,s)}})();var it=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent","ble","webrtc","link","count","lists","serial","common","dm"],d={id:"string"},kn={"relay.subscribe":{dir:"out",fields:{...d,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...d,subId:"string"}},"relay.publish":{dir:"out",fields:{...d,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...d,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...d,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:{...d}},"identity.getRelays":{dir:"out",fields:{...d}},"identity.getProfile":{dir:"out",fields:{...d}},"identity.getFollows":{dir:"out",fields:{...d}},"identity.getList":{dir:"out",fields:{...d,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...d}},"identity.getMutes":{dir:"out",fields:{...d}},"identity.getBlocked":{dir:"out",fields:{...d}},"identity.getBadges":{dir:"out",fields:{...d}},"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:{...d,key:"string"}},"storage.set":{dir:"out",fields:{...d,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...d,key:"string"}},"storage.keys":{dir:"out",fields:{...d}},"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:{...d,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...d,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...d}},"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:{...d}},"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:{...d,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:{...d,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:{...d,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:{...d}},"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:{...d,schema:"object"}},"config.get":{dir:"out",fields:{...d}},"config.subscribe":{dir:"out"},"config.unsubscribe":{dir:"out"},"config.openSettings":{dir:"out"},"config.registerSchema.result":{dir:"in"},"config.values":{dir:"in"},"config.schemaError":{dir:"in"},"resource.info":{dir:"out",fields:{...d}},"resource.bytes":{dir:"out",fields:{...d,url:"string"}},"resource.bytesMany":{dir:"out",fields:{...d,urls:"array"}},"resource.cancel":{dir:"out",fields:{...d}},"resource.info.result":{dir:"in"},"resource.info.error":{dir:"in"},"resource.bytes.result":{dir:"in"},"resource.bytes.error":{dir:"in"},"resource.bytesMany.result":{dir:"in"},"resource.bytesMany.error":{dir:"in"},"cvm.discover":{dir:"out",fields:{...d}},"cvm.request":{dir:"out",fields:{...d,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...d,server:"object"}},"cvm.registry.list":{dir:"out",fields:{...d}},"cvm.registry.has":{dir:"out",fields:{...d,family:"string"}},"cvm.registry.describe":{dir:"out",fields:{...d,family:"string"}},"cvm.registry.call":{dir:"out",fields:{...d,family:"string",tool:"string"}},"cvm.discover.result":{dir:"in"},"cvm.request.result":{dir:"in"},"cvm.close.result":{dir:"in"},"cvm.event":{dir:"in"},"cvm.registry.list.result":{dir:"in"},"cvm.registry.has.result":{dir:"in"},"cvm.registry.describe.result":{dir:"in"},"cvm.registry.call.result":{dir:"in"},"outbox.getEvent":{dir:"out",fields:{...d,eventId:"string"}},"outbox.query":{dir:"out",fields:{...d,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...d,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...d,subId:"string"}},"outbox.publish":{dir:"out",fields:{...d,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...d,target:"present"}},"outbox.getEvent.result":{dir:"in"},"outbox.query.result":{dir:"in"},"outbox.event":{dir:"in"},"outbox.closed":{dir:"in"},"outbox.publish.result":{dir:"in"},"outbox.resolveRelays.result":{dir:"in"},"upload.info":{dir:"out",fields:{...d}},"upload.upload":{dir:"out",fields:{...d,request:"object"}},"upload.status":{dir:"out",fields:{...d,uploadId:"string"}},"upload.info.result":{dir:"in"},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...d,request:"object"}},"intent.available":{dir:"out",fields:{...d,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...d}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"},"ble.open":{dir:"out",fields:{...d,request:"object"}},"ble.services":{dir:"out",fields:{...d,sessionId:"string"}},"ble.read":{dir:"out",fields:{...d,sessionId:"string",target:"object"}},"ble.write":{dir:"out",fields:{...d,sessionId:"string",target:"object",data:"array"}},"ble.subscribe":{dir:"out",fields:{...d,sessionId:"string",target:"object"}},"ble.unsubscribe":{dir:"out",fields:{...d,sessionId:"string",target:"object"}},"ble.close":{dir:"out",fields:{...d,sessionId:"string"}},"ble.open.result":{dir:"in"},"ble.services.result":{dir:"in"},"ble.read.result":{dir:"in"},"ble.write.result":{dir:"in"},"ble.subscribe.result":{dir:"in"},"ble.unsubscribe.result":{dir:"in"},"ble.close.result":{dir:"in"},"ble.event":{dir:"in"},"common.encodeNip19":{dir:"out",fields:{...d,input:"object"}},"common.decodeNip19":{dir:"out",fields:{...d,value:"string"}},"common.getProfile":{dir:"out",fields:{...d,target:"string"}},"common.follows":{dir:"out",fields:{...d}},"common.follow":{dir:"out",fields:{...d,pubkeys:"array"}},"common.unfollow":{dir:"out",fields:{...d,pubkeys:"array"}},"common.react":{dir:"out",fields:{...d,targetEventId:"string",reaction:"string"}},"common.report":{dir:"out",fields:{...d,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:{...d,request:"object"}},"webrtc.send":{dir:"out",fields:{...d,sessionId:"string",payload:"present"}},"webrtc.close":{dir:"out",fields:{...d,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:{...d,url:"string"}},"link.open.result":{dir:"in"},"count.query":{dir:"out",fields:{...d,filters:"array"}},"count.query.result":{dir:"in"},"lists.supported":{dir:"out",fields:{...d}},"lists.add":{dir:"out",fields:{...d,list:"object",items:"array"}},"lists.remove":{dir:"out",fields:{...d,list:"object",items:"array"}},"lists.supported.result":{dir:"in"},"lists.add.result":{dir:"in"},"lists.remove.result":{dir:"in"},"serial.open":{dir:"out",fields:{...d,request:"object"}},"serial.write":{dir:"out",fields:{...d,sessionId:"string",data:"array"}},"serial.close":{dir:"out",fields:{...d,sessionId:"string"}},"serial.open.result":{dir:"in"},"serial.write.result":{dir:"in"},"serial.close.result":{dir:"in"},"serial.event":{dir:"in"},"dm.status":{dir:"out",fields:{...d}},"dm.conversations":{dir:"out",fields:{...d}},"dm.messages":{dir:"out",fields:{...d,conversationId:"string"}},"dm.send":{dir:"out",fields:{...d,recipients:"array",content:"string"}},"dm.subscribe":{dir:"out",fields:{...d}},"dm.unsubscribe":{dir:"out",fields:{...d,subscriptionId:"string"}},"dm.status.result":{dir:"in"},"dm.conversations.result":{dir:"in"},"dm.messages.result":{dir:"in"},"dm.send.result":{dir:"in"},"dm.subscribe.result":{dir:"in"},"dm.unsubscribe.result":{dir:"in"},"dm.message":{dir:"in"}};function _t(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 Sn(e,t){return t==="present"?e!=null:_t(e)===t}function Rn(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,n=r.type;if(typeof n!="string")return{ok:!1,errors:[{code:"missing-type",message:"Envelope is missing a string `type` field"}]};const i=n.indexOf(".");if(i<=0)return{ok:!1,type:n,errors:[{code:"malformed-type",message:`Envelope type "${n}" is not in domain.action form`}]};const s=n.slice(0,i);if(!it.includes(s))return{ok:!1,type:n,domain:s,errors:[{code:"unknown-domain",message:`"${s}" is not a known NAP domain`}]};const c=kn[n];if(!c)return{ok:!1,type:n,domain:s,errors:[{code:"unknown-type",message:`"${n}" is not a known ${s} message type`}]};if(c.dir==="in")return{ok:!1,type:n,domain:s,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${n}" is a shell→napplet message; a napplet must not emit it`}]};for(const[a,l]of Object.entries(c.fields??{})){if(!(a in r)||r[a]===void 0){t.push({code:"missing-field",message:`Required field "${a}" is missing`,field:a});continue}Sn(r[a],l)||t.push({code:"wrong-type",message:`Field "${a}" should be ${l} but is ${_t(r[a])}`,field:a})}return{ok:t.length===0,type:n,domain:s,direction:"out",errors:t}}var Ct=5129,Ut=15129,ge=35129,st=[Ct,Ut,ge];function Ht(e,t){return e.tags.find(r=>r[0]===t)}function $n(e){return st.includes(e)}function Tn(e){return typeof e=="string"&&/^[0-9a-f]{64}$/i.test(e)}function jt(e){return Ht(e,"d")?.[1]?.trim()||void 0}function Bn(e){return e.tags.filter(t=>t[0]==="requires").map(t=>t[1]?.trim()??"").filter(Boolean)}function Mt(e){return jt(e)??Ht(e,"title")?.[1]??e.id}function ot(e){const t=[],r=[];if(!e)return{ok:!1,requires:[],errors:[{code:"missing-manifest-event",message:"No NIP-5D manifest event was resolved"}],warnings:r};const n=jt(e),i=Bn(e);$n(e.kind)||t.push({code:"invalid-napplet-kind",message:`Manifest event kind ${e.kind} is not a NIP-5D napplet kind`}),e.kind===ge&&!n?t.push({code:"missing-d-tag",message:"Named napplet manifest kind 35129 must include a non-empty d tag"}):(e.kind===Ut||e.kind===Ct)&&n&&t.push({code:"unexpected-d-tag",message:`Napplet manifest kind ${e.kind} must not include a d tag`});const s=e.tags.find(o=>o[0]==="path"&&o[1]==="/index.html");s?Tn(s[2])||t.push({code:"invalid-index-html-hash",message:"The /index.html path tag must carry a 64-character sha256 hash"}):t.push({code:"missing-index-html",message:"Napplet manifest must include a path tag for /index.html"});for(const o of i){if(o.startsWith("nap:")||o.startsWith("NAP-")){t.push({code:"invalid-required-nap",message:`requires tag "${o}" must be a bare NAP domain such as "relay"`});continue}it.includes(o)||t.push({code:"unknown-required-nap",message:`requires tag "${o}" is not a known NAP domain`})}return{ok:t.length===0,kind:e.kind,dTag:n,nappletType:n,requires:i,errors:t,warnings:r}}var xe="f".repeat(64),In="https://reference.invalid/blob",p=e=>[e],q=()=>[];function bt(e){if(typeof e!="string"||!e.startsWith("data:"))return null;const t=e.indexOf(",");if(t<0)return{blob:new Blob([],{type:"text/plain"}),mime:"text/plain"};const r=e.slice(5,t),n=e.slice(t+1),i=r.split(";").filter(Boolean),s=i.includes("base64"),o=i.find(c=>c.includes("/"))??"text/plain";try{if(s){const c=globalThis.atob(n),a=Uint8Array.from(c,l=>l.charCodeAt(0));return{blob:new Blob([a],{type:o}),mime:o}}return{blob:new Blob([decodeURIComponent(n)],{type:o}),mime:o}}catch{return null}}var An={"relay.subscribe":e=>p({type:"relay.eose",subId:e.subId}),"relay.close":q,"relay.publish":e=>p({type:"relay.publish.result",id:e.id,ok:!0,event:e.event}),"relay.publishEncrypted":e=>p({type:"relay.publishEncrypted.result",id:e.id,ok:!0,event:e.event}),"relay.query":e=>p({type:"relay.query.result",id:e.id,events:[]}),"identity.getPublicKey":e=>p({type:"identity.getPublicKey.result",id:e.id,pubkey:xe}),"identity.getRelays":e=>p({type:"identity.getRelays.result",id:e.id,relays:{}}),"identity.getProfile":e=>p({type:"identity.getProfile.result",id:e.id,profile:null}),"identity.getFollows":e=>p({type:"identity.getFollows.result",id:e.id,pubkeys:[]}),"identity.getList":e=>p({type:"identity.getList.result",id:e.id,entries:[]}),"identity.getZaps":e=>p({type:"identity.getZaps.result",id:e.id,zaps:[]}),"identity.getMutes":e=>p({type:"identity.getMutes.result",id:e.id,pubkeys:[]}),"identity.getBlocked":e=>p({type:"identity.getBlocked.result",id:e.id,pubkeys:[]}),"identity.getBadges":e=>p({type:"identity.getBadges.result",id:e.id,badges:[]}),"storage.get":e=>p({type:"storage.get.result",id:e.id,value:null}),"storage.set":e=>p({type:"storage.set.result",id:e.id}),"storage.remove":e=>p({type:"storage.remove.result",id:e.id}),"storage.keys":e=>p({type:"storage.keys.result",id:e.id,keys:[]}),"inc.emit":q,"inc.subscribe":e=>p({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":q,"inc.channel.open":e=>p({type:"inc.channel.open.result",id:e.id,channelId:`chan-${String(e.id)}`,peer:"reference-peer"}),"inc.channel.emit":q,"inc.channel.broadcast":q,"inc.channel.list":e=>p({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":q,"theme.get":e=>p({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":q,"keys.registerAction":e=>p({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":q,"media.session.create":e=>p({type:"media.session.create.result",id:e.id,sessionId:`session-${String(e.id)}`,owner:e.owner}),"media.session.update":q,"media.session.destroy":q,"media.state":q,"media.capabilities":q,"notify.send":e=>p({type:"notify.send.result",id:e.id,notificationId:`notif-${String(e.id)}`}),"notify.dismiss":q,"notify.badge":q,"notify.channel.register":q,"notify.permission.request":e=>p({type:"notify.permission.result",id:e.id,granted:!0}),"config.registerSchema":e=>p({type:"config.registerSchema.result",id:e.id,ok:!0}),"config.get":e=>p({type:"config.values",id:e.id,values:{}}),"config.subscribe":()=>p({type:"config.values",values:{}}),"config.unsubscribe":q,"config.openSettings":q,"resource.bytes":e=>{const t=bt(e.url);return p(t?{type:"resource.bytes.result",id:e.id,blob:t.blob,mime:t.mime}:{type:"resource.bytes.result",id:e.id,blob:new Blob([]),mime:"application/octet-stream"})},"resource.bytesMany":e=>p({type:"resource.bytesMany.result",id:e.id,items:Array.isArray(e.urls)?e.urls.map(t=>{const r=bt(t);return{url:t,ok:!0,blob:r?.blob??new Blob([]),mime:r?.mime??"application/octet-stream"}}):[]}),"resource.info":e=>p({type:"resource.info.result",id:e.id,info:{schemes:[{scheme:"data",enabled:!0},{scheme:"https",enabled:!0}]}}),"resource.cancel":q,"cvm.discover":e=>p({type:"cvm.discover.result",id:e.id,servers:[]}),"cvm.request":e=>p({type:"cvm.request.result",id:e.id,message:{}}),"cvm.close":e=>p({type:"cvm.close.result",id:e.id}),"outbox.getEvent":e=>p({type:"outbox.getEvent.result",id:e.id}),"outbox.query":e=>p({type:"outbox.query.result",id:e.id,events:[]}),"outbox.subscribe":e=>p({type:"outbox.closed",subId:e.subId,reason:"reference shell complete"}),"outbox.close":q,"outbox.publish":e=>p({type:"outbox.publish.result",id:e.id,ok:!0}),"outbox.resolveRelays":e=>p({type:"outbox.resolveRelays.result",id:e.id,plan:{}}),"upload.info":e=>p({type:"upload.info.result",id:e.id,info:{rails:[{rail:"nip96",enabled:!0,returns:["https"]},{rail:"blossom",enabled:!0,returns:["https","blossom"]}]}}),"upload.upload":e=>p({type:"upload.upload.result",id:e.id,result:{url:In}}),"upload.status":e=>p({type:"upload.status.result",id:e.id,status:{}}),"intent.invoke":e=>p({type:"intent.invoke.result",id:e.id,result:{ok:!0,archetype:"reference",action:"open",handled:!0}}),"intent.available":e=>p({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=>p({type:"intent.handlers.result",id:e.id,handlers:[]}),"ble.open":e=>p({type:"ble.open.result",id:e.id,session:{id:"ble-reference",state:"open",device:{id:"reference-device",name:"Reference BLE"}}}),"ble.services":e=>p({type:"ble.services.result",id:e.id,services:[]}),"ble.read":e=>p({type:"ble.read.result",id:e.id,data:[]}),"ble.write":e=>p({type:"ble.write.result",id:e.id}),"ble.subscribe":e=>p({type:"ble.subscribe.result",id:e.id}),"ble.unsubscribe":e=>p({type:"ble.unsubscribe.result",id:e.id}),"ble.close":e=>p({type:"ble.close.result",id:e.id}),"common.encodeNip19":e=>p({type:"common.encodeNip19.result",id:e.id,ok:!0,value:"npub1reference",nip19Type:"npub"}),"common.decodeNip19":e=>p({type:"common.decodeNip19.result",id:e.id,ok:!0,nip19Type:"npub",hex:xe}),"common.getProfile":e=>p({type:"common.getProfile.result",id:e.id,ok:!0,pubkey:xe,profile:null}),"common.follows":e=>p({type:"common.follows.result",id:e.id,ok:!0,pubkeys:[]}),"common.follow":e=>p({type:"common.follow.result",id:e.id,ok:!0}),"common.unfollow":e=>p({type:"common.unfollow.result",id:e.id,ok:!0}),"common.react":e=>p({type:"common.react.result",id:e.id,ok:!0,eventId:"0".repeat(64)}),"common.report":e=>p({type:"common.report.result",id:e.id,ok:!0,eventId:"1".repeat(64)}),"webrtc.open":e=>p({type:"webrtc.open.result",id:e.id,session:{id:"webrtc-reference",scope:{type:"direct",pubkey:xe},channel:"default",state:"connecting"}}),"webrtc.send":e=>p({type:"webrtc.send.result",id:e.id}),"webrtc.close":e=>p({type:"webrtc.close.result",id:e.id}),"link.open":e=>p({type:"link.open.result",id:e.id,status:"opened"}),"count.query":e=>p({type:"count.query.result",id:e.id,ok:!0,count:0}),"lists.supported":e=>p({type:"lists.supported.result",id:e.id,lists:[]}),"lists.add":e=>p({type:"lists.add.result",id:e.id,ok:!0,added:0,skipped:0}),"lists.remove":e=>p({type:"lists.remove.result",id:e.id,ok:!0,removed:0,skipped:0}),"serial.open":e=>p({type:"serial.open.result",id:e.id,session:{id:`serial-${String(e.id)}`,state:"open"}}),"serial.write":e=>p({type:"serial.write.result",id:e.id}),"serial.close":e=>p({type:"serial.close.result",id:e.id})};function On(e={}){const t=e.now??(()=>Date.now()),r=[];function n(i){const s=i&&typeof i=="object"&&typeof i.type=="string"?i.type:void 0;if(r.push({envelope:i,verdict:Rn(i),timestamp:t()}),!s)return[];const o=An[s];return o?o(i):[]}return{get records(){return r},handle:n,reset(){r.length=0}}}function Nn(e){return{manifestHtml:e.manifestHtml,manifestEvent:e.manifestEvent??null,sandbox:e.sandbox??{allowScripts:!0,allowSameOrigin:!1},installedGlobal:e.boot.installedGlobal,bootError:e.boot.bootError,emitted:e.boot.emitted,forbiddenGlobals:e.forbiddenGlobals??[],degraded:e.boot.degraded,lifecycle:e.lifecycle??null}}var Dt="__nappletConformance.error";function mt(e){return new Promise(t=>setTimeout(t,e))}function qn(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}function Ln(e){return`
|
|
2
|
+
(() => {
|
|
3
|
+
const domains = ${JSON.stringify(e)};
|
|
4
|
+
const napplet = {};
|
|
5
|
+
for (const domain of domains) napplet[domain] = {};
|
|
6
|
+
const pending = new Map();
|
|
7
|
+
let seq = 0;
|
|
8
|
+
const nextId = () => {
|
|
9
|
+
try {
|
|
10
|
+
if (window.crypto && typeof window.crypto.randomUUID === 'function') return window.crypto.randomUUID();
|
|
11
|
+
} catch {}
|
|
12
|
+
seq += 1;
|
|
13
|
+
return 'conformance-' + Date.now().toString(36) + '-' + seq.toString(36);
|
|
14
|
+
};
|
|
15
|
+
const request = (message, resultType, errorType) => new Promise((resolve, reject) => {
|
|
16
|
+
const id = message.id || nextId();
|
|
17
|
+
const timeout = window.setTimeout(() => {
|
|
18
|
+
pending.delete(id);
|
|
19
|
+
reject(new Error(message.type + ' timed out'));
|
|
20
|
+
}, 5000);
|
|
21
|
+
pending.set(id, { resolve, reject, resultType, errorType, timeout });
|
|
22
|
+
window.parent.postMessage({ ...message, id }, '*');
|
|
23
|
+
});
|
|
24
|
+
window.addEventListener('message', (event) => {
|
|
25
|
+
const msg = event.data;
|
|
26
|
+
if (!msg || typeof msg !== 'object' || typeof msg.id !== 'string') return;
|
|
27
|
+
const slot = pending.get(msg.id);
|
|
28
|
+
if (!slot) return;
|
|
29
|
+
if (msg.type !== slot.resultType && msg.type !== slot.errorType) return;
|
|
30
|
+
pending.delete(msg.id);
|
|
31
|
+
window.clearTimeout(slot.timeout);
|
|
32
|
+
if (msg.type === slot.errorType) {
|
|
33
|
+
slot.reject(new Error(msg.message ? msg.error + ': ' + msg.message : String(msg.error || 'request failed')));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
slot.resolve(msg);
|
|
37
|
+
});
|
|
38
|
+
if (napplet.resource) {
|
|
39
|
+
napplet.resource.info = () =>
|
|
40
|
+
request({ type: 'resource.info' }, 'resource.info.result', 'resource.info.error')
|
|
41
|
+
.then((msg) => msg.info);
|
|
42
|
+
napplet.resource.bytes = (url) =>
|
|
43
|
+
request({ type: 'resource.bytes', url: String(url) }, 'resource.bytes.result', 'resource.bytes.error')
|
|
44
|
+
.then((msg) => msg.blob);
|
|
45
|
+
napplet.resource.bytesMany = (urls) =>
|
|
46
|
+
request({ type: 'resource.bytesMany', urls: Array.from(urls || []) }, 'resource.bytesMany.result', 'resource.bytesMany.error')
|
|
47
|
+
.then((msg) => msg.items);
|
|
48
|
+
napplet.resource.bytesAsObjectURL = (url) => {
|
|
49
|
+
const handle = { url: '', revoke: () => {} };
|
|
50
|
+
let objectUrl = '';
|
|
51
|
+
let revoked = false;
|
|
52
|
+
const ready = napplet.resource.bytes(url).then((blob) => {
|
|
53
|
+
if (revoked) return;
|
|
54
|
+
objectUrl = URL.createObjectURL(blob);
|
|
55
|
+
handle.url = objectUrl;
|
|
56
|
+
return objectUrl;
|
|
57
|
+
});
|
|
58
|
+
handle.revoke = () => {
|
|
59
|
+
if (revoked) return;
|
|
60
|
+
revoked = true;
|
|
61
|
+
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
62
|
+
};
|
|
63
|
+
Object.defineProperty(handle, 'ready', { value: ready, enumerable: false });
|
|
64
|
+
return handle;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
window.napplet = napplet;
|
|
68
|
+
const report = (reason) => {
|
|
69
|
+
try {
|
|
70
|
+
window.parent.postMessage({ type: '${Dt}', reason: String(reason && reason.message || reason) }, '*');
|
|
71
|
+
} catch {}
|
|
72
|
+
};
|
|
73
|
+
window.addEventListener('error', (event) => report(event.error || event.message));
|
|
74
|
+
window.addEventListener('unhandledrejection', (event) => report(event.reason));
|
|
75
|
+
})();
|
|
76
|
+
`}function Pn(e,t,r){const n=`<base href="${qn(t)}">`,i=`<script>${Ln(r)}<\/script>`,s=/<head(?:\s[^>]*)?>/i.exec(e);if(s){const o=s.index+s[0].length;return`${e.slice(0,o)}${n}${i}${e.slice(o)}`}return`${n}${i}${e}`}function _n(e){if(!e.startsWith("data:"))return null;const t=e.indexOf(",");if(t<0)return"";const r=e.slice(0,t),n=e.slice(t+1);return r.endsWith(";base64")?atob(n):decodeURIComponent(n)}async function Cn(e){if(e==="about:blank")return"<!doctype html><html><head></head><body></body></html>";const t=_n(e);if(t!==null)return t;const r=await fetch(e);if(!r.ok)throw new Error(`Failed to fetch napplet HTML: ${r.status} ${r.statusText}`);return r.text()}async function yt(e,t,r,n){const i=On(),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 o=null;const c=u=>{if(u.source!==s.contentWindow)return;const f=u.data;if(f&&typeof f=="object"&&f.type===Dt){o=`napplet boot error: ${String(f.reason??"unknown error")}`;return}const g=i.handle(u.data),b=u.source;for(const y of g)b?.postMessage(y,"*")};let a=!1;const l=new Promise(u=>{s.addEventListener("load",()=>{a=!0,u()},{once:!0})});n.addEventListener("message",c),(r.body??r.documentElement).appendChild(s);try{const u=await Cn(t.url);s.srcdoc=Pn(u,t.url,e)}catch(u){o=u instanceof Error?u.message:String(u)}if(!o){const u=Symbol("timeout");await Promise.race([l.then(()=>"loaded"),mt(t.readyTimeoutMs).then(()=>u)])===u||!a?o=`napplet did not boot (iframe did not load within ${t.readyTimeoutMs}ms; a conformant napplet must run under sandbox="allow-scripts" without allow-same-origin)`:await mt(t.settleMs)}n.removeEventListener("message",c);try{s.remove()}catch{}return{installedGlobal:o===null,bootError:o,emitted:[...i.records]}}async function Un(e){const t=e.document??document,r=e.window??window,n={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},i=await yt(it,n,t,r);let s=null;{const o=await yt([],n,t,r);s={bootError:o.bootError,emitted:o.emitted}}return{installedGlobal:i.installedGlobal,bootError:i.bootError,emitted:i.emitted,degraded:s}}var Hn=e=>({status:"pass",detail:e}),jn=(e,t)=>({status:"fail",detail:e,diagnostics:t}),Mn=e=>({status:"skip",detail:e}),O={pass:Hn,fail:jn,skip:Mn};function Dn(e,t){return ot(e.manifestEvent).errors.filter(r=>t.includes(r.code))}function He(e,t,r){return{id:e,area:"manifest",severity:"error",title:t,run:n=>{if(!n.manifestEvent)return O.skip("No NIP-5D manifest event was resolved");const i=Dn(n,r);return i.length?O.fail(i[0].message,i):O.pass()}}}var Zn=[He("manifest/event-kind","Resolved event is a NIP-5D napplet manifest",["invalid-napplet-kind","missing-d-tag","unexpected-d-tag"]),He("manifest/index-html","Manifest declares a hashed /index.html",["missing-index-html","invalid-index-html-hash"]),He("manifest/requires","requires tags are bare known NAP domains",["invalid-required-nap","unknown-required-nap"]),{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?O.fail("Napplet relied on allow-same-origin, which a shell must not grant"):O.pass():O.fail("Napplet was not loaded with allow-scripts")},{id:"boot/installs-global",area:"boot",severity:"error",title:"Runs after runtime-injected window.napplet is installed",run:e=>e.installedGlobal?O.pass():O.fail("runtime did not inject window.napplet before load")},{id:"boot/no-boot-error",area:"boot",severity:"error",title:"Boots without an uncaught error",run:e=>e.bootError?O.fail(e.bootError):O.pass()},{id:"boot/no-forbidden-globals",area:"boot",severity:"error",title:"Does not access forbidden browser authority surfaces",run:e=>e.forbiddenGlobals.length?O.fail(`Accessed forbidden surface(s): ${e.forbiddenGlobals.join(", ")}`,e.forbiddenGlobals):O.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 O.skip("Napplet emitted no envelopes");const t=e.emitted.filter(n=>!n.verdict.ok);if(t.length===0)return O.pass(`${e.emitted.length} envelope(s) valid`);const r=t.map(n=>({envelope:n.envelope,errors:n.verdict.errors}));return O.fail(`${t.length}/${e.emitted.length} emitted envelope(s) invalid`,r)}},{id:"degrade/domain-absence",area:"degradation",severity:"error",title:"Does not crash when no NAP domains are injected",run:e=>e.degraded?e.degraded.bootError?O.fail(`Crashed with no injected domains: ${e.degraded.bootError}`):O.pass():O.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?O.fail("Message listeners remained after unload"):O.pass():O.skip("Lifecycle not measured")}];function Wn(e,t={}){const r=t.now??(()=>Date.now()),n=t.checks??Zn,i=r(),s=n.map(a=>{const l=a.run(e);return{id:a.id,area:a.area,severity:a.severity,title:a.title,status:l.status,detail:l.detail,diagnostics:l.diagnostics}}),o={total:s.length,passed:s.filter(a=>a.status==="pass").length,failed:s.filter(a=>a.status==="fail").length,skipped:s.filter(a=>a.status==="skip").length,errors:s.filter(a=>a.status==="fail"&&a.severity==="error").length,warnings:s.filter(a=>a.status==="fail"&&a.severity==="warning").length};return{napplet:e.manifestEvent?Mt(e.manifestEvent):void 0,startedAt:i,finishedAt:r(),checks:s,summary:o,ok:o.errors===0}}/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */function Vn(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name==="Uint8Array"}function at(e,t=""){if(!Number.isSafeInteger(e)||e<0){const r=t&&`"${t}" `;throw new Error(`${r}expected integer >= 0, got ${e}`)}}function M(e,t,r=""){const n=Vn(e),i=e?.length,s=t!==void 0;if(!n||s&&i!==t){const o=r&&`"${r}" `,c=s?` of length ${t}`:"",a=n?`length=${i}`:`type=${typeof e}`;throw new Error(o+"expected Uint8Array"+c+", got "+a)}return e}function wt(e,t=!0){if(e.destroyed)throw new Error("Hash instance has been destroyed");if(t&&e.finished)throw new Error("Hash#digest() has already been called")}function Kn(e,t){M(e,void 0,"digestInto() output");const r=t.outputLen;if(e.length<r)throw new Error('"digestInto() output" expected to be of length >='+r)}function ze(...e){for(let t=0;t<e.length;t++)e[t].fill(0)}function je(e){return new DataView(e.buffer,e.byteOffset,e.byteLength)}function z(e,t){return e<<32-t|e>>>t}const Zt=typeof Uint8Array.from([]).toHex=="function"&&typeof Uint8Array.fromHex=="function",Fn=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));function _(e){if(M(e),Zt)return e.toHex();let t="";for(let r=0;r<e.length;r++)t+=Fn[e[r]];return t}const J={_0:48,_9:57,A:65,F:70,a:97,f:102};function vt(e){if(e>=J._0&&e<=J._9)return e-J._0;if(e>=J.A&&e<=J.F)return e-(J.A-10);if(e>=J.a&&e<=J.f)return e-(J.a-10)}function X(e){if(typeof e!="string")throw new Error("hex string expected, got "+typeof e);if(Zt)return Uint8Array.fromHex(e);const t=e.length,r=t/2;if(t%2)throw new Error("hex string expected, got unpadded hex of length "+t);const n=new Uint8Array(r);for(let i=0,s=0;i<r;i++,s+=2){const o=vt(e.charCodeAt(s)),c=vt(e.charCodeAt(s+1));if(o===void 0||c===void 0){const a=e[s]+e[s+1];throw new Error('hex string expected, got non-hex character "'+a+'" at index '+s)}n[i]=o*16+c}return n}function Be(...e){let t=0;for(let n=0;n<e.length;n++){const i=e[n];M(i),t+=i.length}const r=new Uint8Array(t);for(let n=0,i=0;n<e.length;n++){const s=e[n];r.set(s,i),i+=s.length}return r}function Yn(e,t={}){const r=(i,s)=>e(s).update(i).digest(),n=e(void 0);return r.outputLen=n.outputLen,r.blockLen=n.blockLen,r.create=i=>e(i),Object.assign(r,t),Object.freeze(r)}function Wt(e=32){const t=typeof globalThis=="object"?globalThis.crypto:null;if(typeof t?.getRandomValues!="function")throw new Error("crypto.getRandomValues must be defined");return t.getRandomValues(new Uint8Array(e))}const zn=e=>({oid:Uint8Array.from([6,9,96,134,72,1,101,3,4,2,e])});/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */function Vt(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name==="Uint8Array"}function Kt(e,t){return Array.isArray(t)?t.length===0?!0:e?t.every(r=>typeof r=="string"):t.every(r=>Number.isSafeInteger(r)):!1}function Gn(e){if(typeof e!="function")throw new Error("function expected");return!0}function me(e,t){if(typeof t!="string")throw new Error(`${e}: string expected`);return!0}function Ft(e){if(!Number.isSafeInteger(e))throw new Error(`invalid integer: ${e}`)}function Ge(e){if(!Array.isArray(e))throw new Error("array expected")}function Yt(e,t){if(!Kt(!0,t))throw new Error(`${e}: array of strings expected`)}function zt(e,t){if(!Kt(!1,t))throw new Error(`${e}: array of numbers expected`)}function Xn(...e){const t=s=>s,r=(s,o)=>c=>s(o(c)),n=e.map(s=>s.encode).reduceRight(r,t),i=e.map(s=>s.decode).reduce(r,t);return{encode:n,decode:i}}function Jn(e){const t=typeof e=="string"?e.split(""):e,r=t.length;Yt("alphabet",t);const n=new Map(t.map((i,s)=>[i,s]));return{encode:i=>(Ge(i),i.map(s=>{if(!Number.isSafeInteger(s)||s<0||s>=r)throw new Error(`alphabet.encode: digit index outside alphabet "${s}". Allowed: ${e}`);return t[s]})),decode:i=>(Ge(i),i.map(s=>{me("alphabet.decode",s);const o=n.get(s);if(o===void 0)throw new Error(`Unknown letter: "${s}". Allowed: ${e}`);return o}))}}function Qn(e=""){return me("join",e),{encode:t=>(Yt("join.decode",t),t.join(e)),decode:t=>(me("join.decode",t),t.split(e))}}const Gt=(e,t)=>t===0?e:Gt(t,e%t),Ie=(e,t)=>e+(t-Gt(e,t)),$e=(()=>{let e=[];for(let t=0;t<40;t++)e.push(2**t);return e})();function Xe(e,t,r,n){if(Ge(e),t<=0||t>32)throw new Error(`convertRadix2: wrong from=${t}`);if(r<=0||r>32)throw new Error(`convertRadix2: wrong to=${r}`);if(Ie(t,r)>32)throw new Error(`convertRadix2: carry overflow from=${t} to=${r} carryBits=${Ie(t,r)}`);let i=0,s=0;const o=$e[t],c=$e[r]-1,a=[];for(const l of e){if(Ft(l),l>=o)throw new Error(`convertRadix2: invalid data word=${l} from=${t}`);if(i=i<<t|l,s+t>32)throw new Error(`convertRadix2: carry overflow pos=${s} from=${t}`);for(s+=t;s>=r;s-=r)a.push((i>>s-r&c)>>>0);const u=$e[s];if(u===void 0)throw new Error("invalid carry");i&=u-1}if(i=i<<r-s&c,!n&&s>=t)throw new Error("Excess padding");if(!n&&i>0)throw new Error(`Non-zero padding: ${i}`);return n&&s>0&&a.push(i>>>0),a}function er(e,t=!1){if(Ft(e),e<=0||e>32)throw new Error("radix2: bits should be in (0..32]");if(Ie(8,e)>32||Ie(e,8)>32)throw new Error("radix2: carry overflow");return{encode:r=>{if(!Vt(r))throw new Error("radix2.encode input should be Uint8Array");return Xe(Array.from(r),8,e,!t)},decode:r=>(zt("radix2.decode",r),Uint8Array.from(Xe(r,e,8,t)))}}function Et(e){return Gn(e),function(...t){try{return e.apply(null,t)}catch{}}}const Je=Xn(Jn("qpzry9x8gf2tvdw0s3jn54khce6mua7l"),Qn("")),xt=[996825010,642813549,513874426,1027748829,705979059];function fe(e){const t=e>>25;let r=(e&33554431)<<5;for(let n=0;n<xt.length;n++)(t>>n&1)===1&&(r^=xt[n]);return r}function kt(e,t,r=1){const n=e.length;let i=1;for(let s=0;s<n;s++){const o=e.charCodeAt(s);if(o<33||o>126)throw new Error(`Invalid prefix (${e})`);i=fe(i)^o>>5}i=fe(i);for(let s=0;s<n;s++)i=fe(i)^e.charCodeAt(s)&31;for(let s of t)i=fe(i)^s;for(let s=0;s<6;s++)i=fe(i);return i^=r,Je.encode(Xe([i%$e[30]],30,5,!1))}function tr(e){const t=e==="bech32"?1:734539939,r=er(5),n=r.decode,i=r.encode,s=Et(n);function o(f,g,b=90){me("bech32.encode prefix",f),Vt(g)&&(g=Array.from(g)),zt("bech32.encode",g);const y=f.length;if(y===0)throw new TypeError(`Invalid prefix length ${y}`);const E=y+7+g.length;if(b!==!1&&E>b)throw new TypeError(`Length ${E} exceeds limit ${b}`);const N=f.toLowerCase(),L=kt(N,g,t);return`${N}1${Je.encode(g)}${L}`}function c(f,g=90){me("bech32.decode input",f);const b=f.length;if(b<8||g!==!1&&b>g)throw new TypeError(`invalid string length: ${b} (${f}). Expected (8..${g})`);const y=f.toLowerCase();if(f!==y&&f!==f.toUpperCase())throw new Error("String must be lowercase or uppercase");const E=y.lastIndexOf("1");if(E===0||E===-1)throw new Error('Letter "1" must be present between prefix and data only');const N=y.slice(0,E),L=y.slice(E+1);if(L.length<6)throw new Error("Data must be at least 6 characters long");const F=Je.decode(L).slice(0,-6),Q=kt(N,F,t);if(!L.endsWith(Q))throw new Error(`Invalid checksum in ${f}: expected "${Q}"`);return{prefix:N,words:F}}const a=Et(c);function l(f){const{prefix:g,words:b}=c(f,!1);return{prefix:g,words:b,bytes:n(b)}}function u(f,g){return o(f,i(g))}return{encode:o,decode:c,encodeFromBytes:u,decodeToBytes:l,decodeUnsafe:a,fromWords:n,fromWordsUnsafe:s,toWords:i}}const St=tr("bech32");var ke=new TextDecoder("utf-8");new TextEncoder;var nr=5e3;function rr(e){try{return e.startsWith("nostr:")&&(e=e.substring(6)),ir(e)}catch{return{type:"invalid",data:null}}}function ir(e){let{prefix:t,words:r}=St.decode(e,nr),n=new Uint8Array(St.fromWords(r));switch(t){case"nprofile":{let i=Me(n);if(!i[0]?.[0])throw new Error("missing TLV 0 for nprofile");if(i[0][0].length!==32)throw new Error("TLV 0 should be 32 bytes");return{type:"nprofile",data:{pubkey:_(i[0][0]),relays:i[1]?i[1].map(s=>ke.decode(s)):[]}}}case"nevent":{let i=Me(n);if(!i[0]?.[0])throw new Error("missing TLV 0 for nevent");if(i[0][0].length!==32)throw new Error("TLV 0 should be 32 bytes");if(i[2]&&i[2][0].length!==32)throw new Error("TLV 2 should be 32 bytes");if(i[3]&&i[3][0].length!==4)throw new Error("TLV 3 should be 4 bytes");return{type:"nevent",data:{id:_(i[0][0]),relays:i[1]?i[1].map(s=>ke.decode(s)):[],author:i[2]?.[0]?_(i[2][0]):void 0,kind:i[3]?.[0]?parseInt(_(i[3][0]),16):void 0}}}case"naddr":{let i=Me(n);if(!i[0]?.[0])throw new Error("missing TLV 0 for naddr");if(!i[2]?.[0])throw new Error("missing TLV 2 for naddr");if(i[2][0].length!==32)throw new Error("TLV 2 should be 32 bytes");if(!i[3]?.[0])throw new Error("missing TLV 3 for naddr");if(i[3][0].length!==4)throw new Error("TLV 3 should be 4 bytes");return{type:"naddr",data:{identifier:ke.decode(i[0][0]),pubkey:_(i[2][0]),kind:parseInt(_(i[3][0]),16),relays:i[1]?i[1].map(s=>ke.decode(s)):[]}}}case"nsec":return{type:t,data:n};case"npub":case"note":return{type:t,data:_(n)};default:throw new Error(`unknown prefix ${t}`)}}function Me(e){let t={},r=e;for(;r.length>0;){let n=r[0],i=r[1],s=r.slice(2,2+i);if(r=r.slice(2+i),s.length<i)throw new Error(`not enough data to read on TLV ${n}`);t[n]=t[n]||[],t[n].push(s)}return t}function sr(e,t,r){return e&t^~e&r}function or(e,t,r){return e&t^e&r^t&r}class ar{blockLen;outputLen;padOffset;isLE;buffer;view;finished=!1;length=0;pos=0;destroyed=!1;constructor(t,r,n,i){this.blockLen=t,this.outputLen=r,this.padOffset=n,this.isLE=i,this.buffer=new Uint8Array(t),this.view=je(this.buffer)}update(t){wt(this),M(t);const{view:r,buffer:n,blockLen:i}=this,s=t.length;for(let o=0;o<s;){const c=Math.min(i-this.pos,s-o);if(c===i){const a=je(t);for(;i<=s-o;o+=i)this.process(a,o);continue}n.set(t.subarray(o,o+c),this.pos),this.pos+=c,o+=c,this.pos===i&&(this.process(r,0),this.pos=0)}return this.length+=t.length,this.roundClean(),this}digestInto(t){wt(this),Kn(t,this),this.finished=!0;const{buffer:r,view:n,blockLen:i,isLE:s}=this;let{pos:o}=this;r[o++]=128,ze(this.buffer.subarray(o)),this.padOffset>i-o&&(this.process(n,0),o=0);for(let f=o;f<i;f++)r[f]=0;n.setBigUint64(i-8,BigInt(this.length*8),s),this.process(n,0);const c=je(t),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen must be aligned to 32bit");const l=a/4,u=this.get();if(l>u.length)throw new Error("_sha2: outputLen bigger than state");for(let f=0;f<l;f++)c.setUint32(4*f,u[f],s)}digest(){const{buffer:t,outputLen:r}=this;this.digestInto(t);const n=t.slice(0,r);return this.destroy(),n}_cloneInto(t){t||=new this.constructor,t.set(...this.get());const{blockLen:r,buffer:n,length:i,finished:s,destroyed:o,pos:c}=this;return t.destroyed=o,t.finished=s,t.length=i,t.pos=c,i%r&&t.buffer.set(n),t}clone(){return this._cloneInto()}}const ee=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),cr=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),te=new Uint32Array(64);class lr extends ar{constructor(t){super(64,t,8,!1)}get(){const{A:t,B:r,C:n,D:i,E:s,F:o,G:c,H:a}=this;return[t,r,n,i,s,o,c,a]}set(t,r,n,i,s,o,c,a){this.A=t|0,this.B=r|0,this.C=n|0,this.D=i|0,this.E=s|0,this.F=o|0,this.G=c|0,this.H=a|0}process(t,r){for(let f=0;f<16;f++,r+=4)te[f]=t.getUint32(r,!1);for(let f=16;f<64;f++){const g=te[f-15],b=te[f-2],y=z(g,7)^z(g,18)^g>>>3,E=z(b,17)^z(b,19)^b>>>10;te[f]=E+te[f-7]+y+te[f-16]|0}let{A:n,B:i,C:s,D:o,E:c,F:a,G:l,H:u}=this;for(let f=0;f<64;f++){const g=z(c,6)^z(c,11)^z(c,25),b=u+g+sr(c,a,l)+cr[f]+te[f]|0,E=(z(n,2)^z(n,13)^z(n,22))+or(n,i,s)|0;u=l,l=a,a=c,c=o+b|0,o=s,s=i,i=n,n=b+E|0}n=n+this.A|0,i=i+this.B|0,s=s+this.C|0,o=o+this.D|0,c=c+this.E|0,a=a+this.F|0,l=l+this.G|0,u=u+this.H|0,this.set(n,i,s,o,c,a,l,u)}roundClean(){ze(te)}destroy(){this.set(0,0,0,0,0,0,0,0),ze(this.buffer)}}class dr extends lr{A=ee[0]|0;B=ee[1]|0;C=ee[2]|0;D=ee[3]|0;E=ee[4]|0;F=ee[5]|0;G=ee[6]|0;H=ee[7]|0;constructor(){super(32)}}const Ae=Yn(()=>new dr,zn(1));/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const ct=BigInt(0),Qe=BigInt(1);function Rt(e,t=""){if(typeof e!="boolean"){const r=t&&`"${t}" `;throw new Error(r+"expected boolean, got type="+typeof e)}return e}function ur(e){if(typeof e=="bigint"){if(!pr(e))throw new Error("positive bigint expected, got "+e)}else at(e);return e}function Xt(e){if(typeof e!="string")throw new Error("hex string expected, got "+typeof e);return e===""?ct:BigInt("0x"+e)}function lt(e){return Xt(_(e))}function Jt(e){return Xt(_(fr(M(e)).reverse()))}function dt(e,t){at(t),e=ur(e);const r=X(e.toString(16).padStart(t*2,"0"));if(r.length!==t)throw new Error("number too large");return r}function Qt(e,t){return dt(e,t).reverse()}function fr(e){return Uint8Array.from(e)}function hr(e){return Uint8Array.from(e,(t,r)=>{const n=t.charCodeAt(0);if(t.length!==1||n>127)throw new Error(`string contains non-ASCII character "${e[r]}" with code ${n} at position ${r}`);return n})}const pr=e=>typeof e=="bigint"&&ct<=e;function gr(e){let t;for(t=0;e>ct;e>>=Qe,t+=1);return t}const en=e=>(Qe<<BigInt(e))-Qe;function tn(e,t={},r={}){if(!e||typeof e!="object")throw new Error("expected valid options object");function n(s,o,c){const a=e[s];if(c&&a===void 0)return;const l=typeof a;if(l!==o||a===null)throw new Error(`param "${s}" is invalid: expected ${o}, got ${l}`)}const i=(s,o)=>Object.entries(s).forEach(([c,a])=>n(c,a,o));i(t,!1),i(r,!0)}function $t(e){const t=new WeakMap;return(r,...n)=>{const i=t.get(r);if(i!==void 0)return i;const s=e(r,...n);return t.set(r,s),s}}/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const j=BigInt(0),H=BigInt(1),se=BigInt(2),nn=BigInt(3),rn=BigInt(4),sn=BigInt(5),br=BigInt(7),on=BigInt(8),mr=BigInt(9),an=BigInt(16);function K(e,t){const r=e%t;return r>=j?r:t+r}function Z(e,t,r){let n=e;for(;t-- >j;)n*=n,n%=r;return n}function Tt(e,t){if(e===j)throw new Error("invert: expected non-zero number");if(t<=j)throw new Error("invert: expected positive modulus, got "+t);let r=K(e,t),n=t,i=j,s=H;for(;r!==j;){const c=n/r,a=n%r,l=i-s*c;n=r,r=a,i=s,s=l}if(n!==H)throw new Error("invert: does not exist");return K(i,t)}function ut(e,t,r){if(!e.eql(e.sqr(t),r))throw new Error("Cannot find square root")}function cn(e,t){const r=(e.ORDER+H)/rn,n=e.pow(t,r);return ut(e,n,t),n}function yr(e,t){const r=(e.ORDER-sn)/on,n=e.mul(t,se),i=e.pow(n,r),s=e.mul(t,i),o=e.mul(e.mul(s,se),i),c=e.mul(s,e.sub(o,e.ONE));return ut(e,c,t),c}function wr(e){const t=Le(e),r=ln(e),n=r(t,t.neg(t.ONE)),i=r(t,n),s=r(t,t.neg(n)),o=(e+br)/an;return(c,a)=>{let l=c.pow(a,o),u=c.mul(l,n);const f=c.mul(l,i),g=c.mul(l,s),b=c.eql(c.sqr(u),a),y=c.eql(c.sqr(f),a);l=c.cmov(l,u,b),u=c.cmov(g,f,y);const E=c.eql(c.sqr(u),a),N=c.cmov(l,u,E);return ut(c,N,a),N}}function ln(e){if(e<nn)throw new Error("sqrt is not defined for small field");let t=e-H,r=0;for(;t%se===j;)t/=se,r++;let n=se;const i=Le(e);for(;Bt(i,n)===1;)if(n++>1e3)throw new Error("Cannot find square root: probably non-prime P");if(r===1)return cn;let s=i.pow(n,t);const o=(t+H)/se;return function(a,l){if(a.is0(l))return l;if(Bt(a,l)!==1)throw new Error("Cannot find square root");let u=r,f=a.mul(a.ONE,s),g=a.pow(l,t),b=a.pow(l,o);for(;!a.eql(g,a.ONE);){if(a.is0(g))return a.ZERO;let y=1,E=a.sqr(g);for(;!a.eql(E,a.ONE);)if(y++,E=a.sqr(E),y===u)throw new Error("Cannot find square root");const N=H<<BigInt(u-y-1),L=a.pow(f,N);u=y,f=a.sqr(L),g=a.mul(g,f),b=a.mul(b,L)}return b}}function vr(e){return e%rn===nn?cn:e%on===sn?yr:e%an===mr?wr(e):ln(e)}const Er=["create","isValid","is0","neg","inv","sqrt","sqr","eql","add","sub","mul","pow","div","addN","subN","mulN","sqrN"];function xr(e){const t={ORDER:"bigint",BYTES:"number",BITS:"number"},r=Er.reduce((n,i)=>(n[i]="function",n),t);return tn(e,r),e}function kr(e,t,r){if(r<j)throw new Error("invalid exponent, negatives unsupported");if(r===j)return e.ONE;if(r===H)return t;let n=e.ONE,i=t;for(;r>j;)r&H&&(n=e.mul(n,i)),i=e.sqr(i),r>>=H;return n}function dn(e,t,r=!1){const n=new Array(t.length).fill(r?e.ZERO:void 0),i=t.reduce((o,c,a)=>e.is0(c)?o:(n[a]=o,e.mul(o,c)),e.ONE),s=e.inv(i);return t.reduceRight((o,c,a)=>e.is0(c)?o:(n[a]=e.mul(o,n[a]),e.mul(o,c)),s),n}function Bt(e,t){const r=(e.ORDER-H)/se,n=e.pow(t,r),i=e.eql(n,e.ONE),s=e.eql(n,e.ZERO),o=e.eql(n,e.neg(e.ONE));if(!i&&!s&&!o)throw new Error("invalid Legendre symbol result");return i?1:s?0:-1}function Sr(e,t){t!==void 0&&at(t);const r=t!==void 0?t:e.toString(2).length,n=Math.ceil(r/8);return{nBitLength:r,nByteLength:n}}class Rr{ORDER;BITS;BYTES;isLE;ZERO=j;ONE=H;_lengths;_sqrt;_mod;constructor(t,r={}){if(t<=j)throw new Error("invalid field: expected ORDER > 0, got "+t);let n;this.isLE=!1,r!=null&&typeof r=="object"&&(typeof r.BITS=="number"&&(n=r.BITS),typeof r.sqrt=="function"&&(this.sqrt=r.sqrt),typeof r.isLE=="boolean"&&(this.isLE=r.isLE),r.allowedLengths&&(this._lengths=r.allowedLengths?.slice()),typeof r.modFromBytes=="boolean"&&(this._mod=r.modFromBytes));const{nBitLength:i,nByteLength:s}=Sr(t,n);if(s>2048)throw new Error("invalid field: expected ORDER of <= 2048 bytes");this.ORDER=t,this.BITS=i,this.BYTES=s,this._sqrt=void 0,Object.preventExtensions(this)}create(t){return K(t,this.ORDER)}isValid(t){if(typeof t!="bigint")throw new Error("invalid field element: expected bigint, got "+typeof t);return j<=t&&t<this.ORDER}is0(t){return t===j}isValidNot0(t){return!this.is0(t)&&this.isValid(t)}isOdd(t){return(t&H)===H}neg(t){return K(-t,this.ORDER)}eql(t,r){return t===r}sqr(t){return K(t*t,this.ORDER)}add(t,r){return K(t+r,this.ORDER)}sub(t,r){return K(t-r,this.ORDER)}mul(t,r){return K(t*r,this.ORDER)}pow(t,r){return kr(this,t,r)}div(t,r){return K(t*Tt(r,this.ORDER),this.ORDER)}sqrN(t){return t*t}addN(t,r){return t+r}subN(t,r){return t-r}mulN(t,r){return t*r}inv(t){return Tt(t,this.ORDER)}sqrt(t){return this._sqrt||(this._sqrt=vr(this.ORDER)),this._sqrt(this,t)}toBytes(t){return this.isLE?Qt(t,this.BYTES):dt(t,this.BYTES)}fromBytes(t,r=!1){M(t);const{_lengths:n,BYTES:i,isLE:s,ORDER:o,_mod:c}=this;if(n){if(!n.includes(t.length)||t.length>i)throw new Error("Field.fromBytes: expected "+n+" bytes, got "+t.length);const l=new Uint8Array(i);l.set(t,s?0:l.length-t.length),t=l}if(t.length!==i)throw new Error("Field.fromBytes: expected "+i+" bytes, got "+t.length);let a=s?Jt(t):lt(t);if(c&&(a=K(a,o)),!r&&!this.isValid(a))throw new Error("invalid field element: outside of range 0..ORDER");return a}invertBatch(t){return dn(this,t)}cmov(t,r,n){return n?r:t}}function Le(e,t={}){return new Rr(e,t)}function un(e){if(typeof e!="bigint")throw new Error("field order must be bigint");const t=e.toString(2).length;return Math.ceil(t/8)}function $r(e){const t=un(e);return t+Math.ceil(t/2)}function Tr(e,t,r=!1){M(e);const n=e.length,i=un(t),s=$r(t);if(n<16||n<s||n>1024)throw new Error("expected "+s+"-1024 bytes of input, got "+n);const o=r?Jt(e):lt(e),c=K(o,t-H)+H;return r?Qt(c,i):dt(c,i)}/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const le=BigInt(0),oe=BigInt(1);function Oe(e,t){const r=t.negate();return e?r:t}function It(e,t){const r=dn(e.Fp,t.map(n=>n.Z));return t.map((n,i)=>e.fromAffine(n.toAffine(r[i])))}function fn(e,t){if(!Number.isSafeInteger(e)||e<=0||e>t)throw new Error("invalid window size, expected [1.."+t+"], got W="+e)}function De(e,t){fn(e,t);const r=Math.ceil(t/e)+1,n=2**(e-1),i=2**e,s=en(e),o=BigInt(e);return{windows:r,windowSize:n,mask:s,maxNumber:i,shiftBy:o}}function At(e,t,r){const{windowSize:n,mask:i,maxNumber:s,shiftBy:o}=r;let c=Number(e&i),a=e>>o;c>n&&(c-=s,a+=oe);const l=t*n,u=l+Math.abs(c)-1,f=c===0,g=c<0,b=t%2!==0;return{nextN:a,offset:u,isZero:f,isNeg:g,isNegF:b,offsetF:l}}const Ze=new WeakMap,hn=new WeakMap;function We(e){return hn.get(e)||1}function Ot(e){if(e!==le)throw new Error("invalid wNAF")}class Br{BASE;ZERO;Fn;bits;constructor(t,r){this.BASE=t.BASE,this.ZERO=t.ZERO,this.Fn=t.Fn,this.bits=r}_unsafeLadder(t,r,n=this.ZERO){let i=t;for(;r>le;)r&oe&&(n=n.add(i)),i=i.double(),r>>=oe;return n}precomputeWindow(t,r){const{windows:n,windowSize:i}=De(r,this.bits),s=[];let o=t,c=o;for(let a=0;a<n;a++){c=o,s.push(c);for(let l=1;l<i;l++)c=c.add(o),s.push(c);o=c.double()}return s}wNAF(t,r,n){if(!this.Fn.isValid(n))throw new Error("invalid scalar");let i=this.ZERO,s=this.BASE;const o=De(t,this.bits);for(let c=0;c<o.windows;c++){const{nextN:a,offset:l,isZero:u,isNeg:f,isNegF:g,offsetF:b}=At(n,c,o);n=a,u?s=s.add(Oe(g,r[b])):i=i.add(Oe(f,r[l]))}return Ot(n),{p:i,f:s}}wNAFUnsafe(t,r,n,i=this.ZERO){const s=De(t,this.bits);for(let o=0;o<s.windows&&n!==le;o++){const{nextN:c,offset:a,isZero:l,isNeg:u}=At(n,o,s);if(n=c,!l){const f=r[a];i=i.add(u?f.negate():f)}}return Ot(n),i}getPrecomputes(t,r,n){let i=Ze.get(r);return i||(i=this.precomputeWindow(r,t),t!==1&&(typeof n=="function"&&(i=n(i)),Ze.set(r,i))),i}cached(t,r,n){const i=We(t);return this.wNAF(i,this.getPrecomputes(i,t,n),r)}unsafe(t,r,n,i){const s=We(t);return s===1?this._unsafeLadder(t,r,i):this.wNAFUnsafe(s,this.getPrecomputes(s,t,n),r,i)}createCache(t,r){fn(r,this.bits),hn.set(t,r),Ze.delete(t)}hasCache(t){return We(t)!==1}}function Ir(e,t,r,n){let i=t,s=e.ZERO,o=e.ZERO;for(;r>le||n>le;)r&oe&&(s=s.add(i)),n&oe&&(o=o.add(i)),i=i.double(),r>>=oe,n>>=oe;return{p1:s,p2:o}}function Nt(e,t,r){if(t){if(t.ORDER!==e)throw new Error("Field.ORDER must match order: Fp == p, Fn == n");return xr(t),t}else return Le(e,{isLE:r})}function Ar(e,t,r={},n){if(n===void 0&&(n=e==="edwards"),!t||typeof t!="object")throw new Error(`expected valid ${e} CURVE object`);for(const a of["p","n","h"]){const l=t[a];if(!(typeof l=="bigint"&&l>le))throw new Error(`CURVE.${a} must be positive bigint`)}const i=Nt(t.p,r.Fp,n),s=Nt(t.n,r.Fn,n),c=["Gx","Gy","a","b"];for(const a of c)if(!i.isValid(t[a]))throw new Error(`CURVE.${a} must be valid field element of CURVE.Fp`);return t=Object.freeze(Object.assign({},t)),{CURVE:t,Fp:i,Fn:s}}function Or(e,t){return function(n){const i=e(n);return{secretKey:i,publicKey:t(i)}}}/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const qt=(e,t)=>(e+(e>=0?t:-t)/qr)/t;function Nr(e,t,r){const[[n,i],[s,o]]=t,c=qt(o*e,r),a=qt(-i*e,r);let l=e-c*n-a*s,u=-c*i-a*o;const f=l<he,g=u<he;f&&(l=-l),g&&(u=-u);const b=en(Math.ceil(gr(r)/2))+Te;if(l<he||l>=b||u<he||u>=b)throw new Error("splitScalar (endomorphism): failed, k="+e);return{k1neg:f,k1:l,k2neg:g,k2:u}}const he=BigInt(0),Te=BigInt(1),qr=BigInt(2),Se=BigInt(3),Lr=BigInt(4);function Pr(e,t={}){const r=Ar("weierstrass",e,t),{Fp:n,Fn:i}=r;let s=r.CURVE;const{h:o,n:c}=s;tn(t,{},{allowInfinityPoint:"boolean",clearCofactor:"function",isTorsionFree:"function",fromBytes:"function",toBytes:"function",endo:"object"});const{endo:a}=t;if(a&&(!n.is0(s.a)||typeof a.beta!="bigint"||!Array.isArray(a.basises)))throw new Error('invalid endo: expected "beta": bigint and "basises": array');const l=Cr(n,i);function u(){if(!n.isOdd)throw new Error("compression is not supported: Field does not have .isOdd()")}function f(T,h,m){const{x:v,y:x}=h.toAffine(),B=n.toBytes(v);if(Rt(m,"isCompressed"),m){u();const R=!n.isOdd(x);return Be(_r(R),B)}else return Be(Uint8Array.of(4),B,n.toBytes(x))}function g(T){M(T,void 0,"Point");const{publicKey:h,publicKeyUncompressed:m}=l,v=T.length,x=T[0],B=T.subarray(1);if(v===h&&(x===2||x===3)){const R=n.fromBytes(B);if(!n.isValid(R))throw new Error("bad point: is not on curve, wrong x");const k=E(R);let w;try{w=n.sqrt(k)}catch(D){const P=D instanceof Error?": "+D.message:"";throw new Error("bad point: is not on curve, sqrt error"+P)}u();const S=n.isOdd(w);return(x&1)===1!==S&&(w=n.neg(w)),{x:R,y:w}}else if(v===m&&x===4){const R=n.BYTES,k=n.fromBytes(B.subarray(0,R)),w=n.fromBytes(B.subarray(R,R*2));if(!N(k,w))throw new Error("bad point: is not on curve");return{x:k,y:w}}else throw new Error(`bad point: got length ${v}, expected compressed=${h} or uncompressed=${m}`)}const b=t.toBytes||f,y=t.fromBytes||g;function E(T){const h=n.sqr(T),m=n.mul(h,T);return n.add(n.add(m,n.mul(T,s.a)),s.b)}function N(T,h){const m=n.sqr(h),v=E(T);return n.eql(m,v)}if(!N(s.Gx,s.Gy))throw new Error("bad curve params: generator point");const L=n.mul(n.pow(s.a,Se),Lr),F=n.mul(n.sqr(s.b),BigInt(27));if(n.is0(n.add(L,F)))throw new Error("bad curve params: a or b");function Q(T,h,m=!1){if(!n.isValid(h)||m&&n.is0(h))throw new Error(`bad point coordinate ${T}`);return h}function we(T){if(!(T instanceof A))throw new Error("Weierstrass Point expected")}function ve(T){if(!a||!a.basises)throw new Error("no endo");return Nr(T,a.basises,i.ORDER)}const Ee=$t((T,h)=>{const{X:m,Y:v,Z:x}=T;if(n.eql(x,n.ONE))return{x:m,y:v};const B=T.is0();h==null&&(h=B?n.ONE:n.inv(x));const R=n.mul(m,h),k=n.mul(v,h),w=n.mul(x,h);if(B)return{x:n.ZERO,y:n.ZERO};if(!n.eql(w,n.ONE))throw new Error("invZ was invalid");return{x:R,y:k}}),xn=$t(T=>{if(T.is0()){if(t.allowInfinityPoint&&!n.is0(T.Y))return;throw new Error("bad point: ZERO")}const{x:h,y:m}=T.toAffine();if(!n.isValid(h)||!n.isValid(m))throw new Error("bad point: x or y not field elements");if(!N(h,m))throw new Error("bad point: equation left != right");if(!T.isTorsionFree())throw new Error("bad point: not in prime-order subgroup");return!0});function pt(T,h,m,v,x){return m=new A(n.mul(m.X,T),m.Y,m.Z),h=Oe(v,h),m=Oe(x,m),h.add(m)}class A{static BASE=new A(s.Gx,s.Gy,n.ONE);static ZERO=new A(n.ZERO,n.ONE,n.ZERO);static Fp=n;static Fn=i;X;Y;Z;constructor(h,m,v){this.X=Q("x",h),this.Y=Q("y",m,!0),this.Z=Q("z",v),Object.freeze(this)}static CURVE(){return s}static fromAffine(h){const{x:m,y:v}=h||{};if(!h||!n.isValid(m)||!n.isValid(v))throw new Error("invalid affine point");if(h instanceof A)throw new Error("projective point not allowed");return n.is0(m)&&n.is0(v)?A.ZERO:new A(m,v,n.ONE)}static fromBytes(h){const m=A.fromAffine(y(M(h,void 0,"point")));return m.assertValidity(),m}static fromHex(h){return A.fromBytes(X(h))}get x(){return this.toAffine().x}get y(){return this.toAffine().y}precompute(h=8,m=!0){return ue.createCache(this,h),m||this.multiply(Se),this}assertValidity(){xn(this)}hasEvenY(){const{y:h}=this.toAffine();if(!n.isOdd)throw new Error("Field doesn't support isOdd");return!n.isOdd(h)}equals(h){we(h);const{X:m,Y:v,Z:x}=this,{X:B,Y:R,Z:k}=h,w=n.eql(n.mul(m,k),n.mul(B,x)),S=n.eql(n.mul(v,k),n.mul(R,x));return w&&S}negate(){return new A(this.X,n.neg(this.Y),this.Z)}double(){const{a:h,b:m}=s,v=n.mul(m,Se),{X:x,Y:B,Z:R}=this;let k=n.ZERO,w=n.ZERO,S=n.ZERO,$=n.mul(x,x),D=n.mul(B,B),P=n.mul(R,R),I=n.mul(x,B);return I=n.add(I,I),S=n.mul(x,R),S=n.add(S,S),k=n.mul(h,S),w=n.mul(v,P),w=n.add(k,w),k=n.sub(D,w),w=n.add(D,w),w=n.mul(k,w),k=n.mul(I,k),S=n.mul(v,S),P=n.mul(h,P),I=n.sub($,P),I=n.mul(h,I),I=n.add(I,S),S=n.add($,$),$=n.add(S,$),$=n.add($,P),$=n.mul($,I),w=n.add(w,$),P=n.mul(B,R),P=n.add(P,P),$=n.mul(P,I),k=n.sub(k,$),S=n.mul(P,D),S=n.add(S,S),S=n.add(S,S),new A(k,w,S)}add(h){we(h);const{X:m,Y:v,Z:x}=this,{X:B,Y:R,Z:k}=h;let w=n.ZERO,S=n.ZERO,$=n.ZERO;const D=s.a,P=n.mul(s.b,Se);let I=n.mul(m,B),W=n.mul(v,R),V=n.mul(x,k),ae=n.add(m,v),U=n.add(B,R);ae=n.mul(ae,U),U=n.add(I,W),ae=n.sub(ae,U),U=n.add(m,x);let Y=n.add(B,k);return U=n.mul(U,Y),Y=n.add(I,V),U=n.sub(U,Y),Y=n.add(v,x),w=n.add(R,k),Y=n.mul(Y,w),w=n.add(W,V),Y=n.sub(Y,w),$=n.mul(D,U),w=n.mul(P,V),$=n.add(w,$),w=n.sub(W,$),$=n.add(W,$),S=n.mul(w,$),W=n.add(I,I),W=n.add(W,I),V=n.mul(D,V),U=n.mul(P,U),W=n.add(W,V),V=n.sub(I,V),V=n.mul(D,V),U=n.add(U,V),I=n.mul(W,U),S=n.add(S,I),I=n.mul(Y,U),w=n.mul(ae,w),w=n.sub(w,I),I=n.mul(ae,W),$=n.mul(Y,$),$=n.add($,I),new A(w,S,$)}subtract(h){return this.add(h.negate())}is0(){return this.equals(A.ZERO)}multiply(h){const{endo:m}=t;if(!i.isValidNot0(h))throw new Error("invalid scalar: out of range");let v,x;const B=R=>ue.cached(this,R,k=>It(A,k));if(m){const{k1neg:R,k1:k,k2neg:w,k2:S}=ve(h),{p:$,f:D}=B(k),{p:P,f:I}=B(S);x=D.add(I),v=pt(m.beta,$,P,R,w)}else{const{p:R,f:k}=B(h);v=R,x=k}return It(A,[v,x])[0]}multiplyUnsafe(h){const{endo:m}=t,v=this;if(!i.isValid(h))throw new Error("invalid scalar: out of range");if(h===he||v.is0())return A.ZERO;if(h===Te)return v;if(ue.hasCache(this))return this.multiply(h);if(m){const{k1neg:x,k1:B,k2neg:R,k2:k}=ve(h),{p1:w,p2:S}=Ir(A,v,B,k);return pt(m.beta,w,S,x,R)}else return ue.unsafe(v,h)}toAffine(h){return Ee(this,h)}isTorsionFree(){const{isTorsionFree:h}=t;return o===Te?!0:h?h(A,this):ue.unsafe(this,c).is0()}clearCofactor(){const{clearCofactor:h}=t;return o===Te?this:h?h(A,this):this.multiplyUnsafe(o)}isSmallOrder(){return this.multiplyUnsafe(o).is0()}toBytes(h=!0){return Rt(h,"isCompressed"),this.assertValidity(),b(A,this,h)}toHex(h=!0){return _(this.toBytes(h))}toString(){return`<Point ${this.is0()?"ZERO":this.toHex()}>`}}const gt=i.BITS,ue=new Br(A,t.endo?Math.ceil(gt/2):gt);return A.BASE.precompute(8),A}function _r(e){return Uint8Array.of(e?2:3)}function Cr(e,t){return{secretKey:t.BYTES,publicKey:1+e.BYTES,publicKeyUncompressed:1+2*e.BYTES,publicKeyHasPrefix:!0,signature:2*t.BYTES}}/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const Pe={p:BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),n:BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),h:BigInt(1),a:BigInt(0),b:BigInt(7),Gx:BigInt("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),Gy:BigInt("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")},Ur={beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),basises:[[BigInt("0x3086d221a7d46bcde86c90e49284eb15"),-BigInt("0xe4437ed6010e88286f547fa90abfe4c3")],[BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),BigInt("0x3086d221a7d46bcde86c90e49284eb15")]]},Hr=BigInt(0),et=BigInt(2);function jr(e){const t=Pe.p,r=BigInt(3),n=BigInt(6),i=BigInt(11),s=BigInt(22),o=BigInt(23),c=BigInt(44),a=BigInt(88),l=e*e*e%t,u=l*l*e%t,f=Z(u,r,t)*u%t,g=Z(f,r,t)*u%t,b=Z(g,et,t)*l%t,y=Z(b,i,t)*b%t,E=Z(y,s,t)*y%t,N=Z(E,c,t)*E%t,L=Z(N,a,t)*N%t,F=Z(L,c,t)*E%t,Q=Z(F,r,t)*u%t,we=Z(Q,o,t)*y%t,ve=Z(we,n,t)*l%t,Ee=Z(ve,et,t);if(!Ne.eql(Ne.sqr(Ee),e))throw new Error("Cannot find square root");return Ee}const Ne=Le(Pe.p,{sqrt:jr}),de=Pr(Pe,{Fp:Ne,endo:Ur}),Lt={};function qe(e,...t){let r=Lt[e];if(r===void 0){const n=Ae(hr(e));r=Be(n,n),Lt[e]=r}return Ae(Be(r,...t))}const ft=e=>e.toBytes(!0).slice(1),ht=e=>e%et===Hr;function tt(e){const{Fn:t,BASE:r}=de,n=t.fromBytes(e),i=r.multiply(n);return{scalar:ht(i.y)?n:t.neg(n),bytes:ft(i)}}function pn(e){const t=Ne;if(!t.isValidNot0(e))throw new Error("invalid x: Fail if x ≥ p");const r=t.create(e*e),n=t.create(r*e+BigInt(7));let i=t.sqrt(n);ht(i)||(i=t.neg(i));const s=de.fromAffine({x:e,y:i});return s.assertValidity(),s}const be=lt;function gn(...e){return de.Fn.create(be(qe("BIP0340/challenge",...e)))}function Pt(e){return tt(e).bytes}function Mr(e,t,r=Wt(32)){const{Fn:n}=de,i=M(e,void 0,"message"),{bytes:s,scalar:o}=tt(t),c=M(r,32,"auxRand"),a=n.toBytes(o^be(qe("BIP0340/aux",c))),l=qe("BIP0340/nonce",a,s,i),{bytes:u,scalar:f}=tt(l),g=gn(u,s,i),b=new Uint8Array(64);if(b.set(u,0),b.set(n.toBytes(n.create(f+g*o)),32),!bn(b,i,s))throw new Error("sign: Invalid signature produced");return b}function bn(e,t,r){const{Fp:n,Fn:i,BASE:s}=de,o=M(e,64,"signature"),c=M(t,void 0,"message"),a=M(r,32,"publicKey");try{const l=pn(be(a)),u=be(o.subarray(0,32));if(!n.isValidNot0(u))return!1;const f=be(o.subarray(32,64));if(!i.isValidNot0(f))return!1;const g=gn(i.toBytes(u),ft(l),c),b=s.multiplyUnsafe(f).add(l.multiplyUnsafe(i.neg(g))),{x:y,y:E}=b.toAffine();return!(b.is0()||!ht(E)||y!==u)}catch{return!1}}const G=(()=>{const r=(n=Wt(48))=>Tr(n,Pe.n);return{keygen:Or(r,Pt),getPublicKey:Pt,sign:Mr,verify:bn,Point:de,utils:{randomSecretKey:r,taggedHash:qe,lift_x:pn,pointToBytes:ft},lengths:{secretKey:32,publicKey:32,publicKeyHasPrefix:!1,signature:64,seed:48}}})();var ie=Symbol("verified"),Dr=e=>e instanceof Object;function Zr(e){if(!Dr(e)||typeof e.kind!="number"||typeof e.content!="string"||typeof e.created_at!="number"||typeof e.pubkey!="string"||!e.pubkey.match(/^[a-f0-9]{64}$/)||!Array.isArray(e.tags))return!1;for(let t=0;t<e.tags.length;t++){let r=e.tags[t];if(!Array.isArray(r))return!1;for(let n=0;n<r.length;n++)if(typeof r[n]!="string")return!1}return!0}new TextDecoder("utf-8");var Wr=new TextEncoder;function pe(e){try{e.indexOf("://")===-1&&(e="wss://"+e);let t=new URL(e);return t.protocol==="http:"?t.protocol="ws:":t.protocol==="https:"&&(t.protocol="wss:"),t.pathname=t.pathname.replace(/\/+/g,"/"),t.pathname.endsWith("/")&&(t.pathname=t.pathname.slice(0,-1)),(t.port==="80"&&t.protocol==="ws:"||t.port==="443"&&t.protocol==="wss:")&&(t.port=""),t.searchParams.sort(),t.hash="",t.toString()}catch{throw new Error(`Invalid URL: ${e}`)}}var Vr=class{generateSecretKey(){return G.utils.randomSecretKey()}getPublicKey(t){return _(G.getPublicKey(t))}finalizeEvent(t,r){const n=t;return n.pubkey=_(G.getPublicKey(r)),n.id=Ve(n),n.sig=_(G.sign(X(Ve(n)),r)),n[ie]=!0,n}verifyEvent(t){if(typeof t[ie]=="boolean")return t[ie];try{const r=Ve(t);if(r!==t.id)return t[ie]=!1,!1;const n=G.verify(X(t.sig),X(r),X(t.pubkey));return t[ie]=n,n}catch{return t[ie]=!1,!1}}};function Kr(e){if(!Zr(e))throw new Error("can't serialize event with wrong or missing properties");return JSON.stringify([0,e.pubkey,e.created_at,e.kind,e.tags,e.content])}function Ve(e){let t=Ae(Wr.encode(Kr(e)));return _(t)}var _e=new Vr;_e.generateSecretKey;_e.getPublicKey;_e.finalizeEvent;var Fr=_e.verifyEvent,Yr=22242;function zr(e,t){if(e.ids&&e.ids.indexOf(t.id)===-1||e.kinds&&e.kinds.indexOf(t.kind)===-1||e.authors&&e.authors.indexOf(t.pubkey)===-1)return!1;for(let r in e)if(r[0]==="#"){let n=r.slice(1),i=e[`#${n}`];if(i&&!t.tags.find(([s,o])=>s===r.slice(1)&&i.indexOf(o)!==-1))return!1}return!(e.since&&t.created_at<e.since||e.until&&t.created_at>e.until)}function Gr(e,t){for(let r=0;r<e.length;r++)if(zr(e[r],t))return!0;return!1}function Xr(e,t){let r=t.length+3,n=e.indexOf(`"${t}":`)+r,i=e.slice(n).indexOf('"')+n+1;return e.slice(i,i+64)}function Jr(e){let t=e.slice(0,22).indexOf('"EVENT"');if(t===-1)return null;let r=e.slice(t+7+1).indexOf('"');if(r===-1)return null;let n=t+7+1+r,i=e.slice(n+1,80).indexOf('"');if(i===-1)return null;let s=n+1+i;return e.slice(n+1,s)}function Qr(e,t){return{kind:Yr,created_at:Math.floor(Date.now()/1e3),tags:[["relay",e],["challenge",t]],content:""}}var mn=class extends Error{constructor(e,t){super(`Tried to send message '${e} on a closed connection to ${t}.`),this.name="SendingOnClosedConnection"}},yn=class{url;_connected=!1;onclose=null;onnotice=e=>console.debug(`NOTICE from ${this.url}: ${e}`);onauth;baseEoseTimeout=4400;publishTimeout=4400;pingFrequency=29e3;pingTimeout=2e4;resubscribeBackoff=[1e4,1e4,1e4,2e4,2e4,3e4,6e4];openSubs=new Map;enablePing;enableReconnect;idleSince=Date.now();ongoingOperations=0;reconnectTimeoutHandle;pingIntervalHandle;reconnectAttempts=0;skipReconnection=!1;connectionPromise;openCountRequests=new Map;openEventPublishes=new Map;ws;challenge;authPromise;serial=0;verifyEvent;_WebSocket;constructor(e,t){this.url=pe(e),this.verifyEvent=t.verifyEvent,this._WebSocket=t.websocketImplementation||WebSocket,this.enablePing=t.enablePing,this.enableReconnect=t.enableReconnect||!1}static async connect(e,t){const r=new yn(e,t);return await r.connect(t),r}closeAllSubscriptions(e){for(let[t,r]of this.openSubs)r.close(e);this.openSubs.clear();for(let[t,r]of this.openEventPublishes)r.reject(new Error(e));this.openEventPublishes.clear();for(let[t,r]of this.openCountRequests)r.reject(new Error(e));this.openCountRequests.clear()}get connected(){return this._connected}async reconnect(){const e=this.resubscribeBackoff[Math.min(this.reconnectAttempts,this.resubscribeBackoff.length-1)];this.reconnectAttempts++,this.reconnectTimeoutHandle=setTimeout(async()=>{try{await this.connect()}catch{}},e)}handleHardClose(e){this.pingIntervalHandle&&(clearInterval(this.pingIntervalHandle),this.pingIntervalHandle=void 0),this._connected=!1,this.connectionPromise=void 0,this.idleSince=void 0,this.enableReconnect&&!this.skipReconnection?this.reconnect():(this.onclose?.(),this.closeAllSubscriptions(e))}async connect(e){let t;return this.connectionPromise?this.connectionPromise:(this.challenge=void 0,this.authPromise=void 0,this.skipReconnection=!1,this.connectionPromise=new Promise((r,n)=>{e?.timeout&&(t=setTimeout(()=>{n("connection timed out"),this.connectionPromise=void 0,this.skipReconnection=!0,this.onclose?.(),this.handleHardClose("relay connection timed out")},e.timeout)),e?.abort&&(e.abort.onabort=n);try{this.ws=new this._WebSocket(this.url)}catch(i){clearTimeout(t),n(i);return}this.ws.onopen=()=>{this.reconnectTimeoutHandle&&(clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=void 0),clearTimeout(t),this._connected=!0;const i=this.reconnectAttempts>0;this.reconnectAttempts=0;for(const s of this.openSubs.values()){if(s.eosed=!1,i)for(let o=0;o<s.filters.length;o++)s.lastEmitted&&(s.filters[o].since=s.lastEmitted+1);s.fire()}this.enablePing&&(this.pingIntervalHandle=setInterval(()=>this.pingpong(),this.pingFrequency)),r()},this.ws.onerror=()=>{clearTimeout(t),n("connection failed"),this.connectionPromise=void 0,this.skipReconnection=!0,this.onclose?.(),this.handleHardClose("relay connection failed")},this.ws.onclose=i=>{clearTimeout(t),n(i.message||"websocket closed"),this.handleHardClose("relay connection closed")},this.ws.onmessage=this._onmessage.bind(this)}),this.connectionPromise)}waitForPingPong(){return new Promise(e=>{this.ws.once("pong",()=>e(!0)),this.ws.ping()})}waitForDummyReq(){return new Promise((e,t)=>{if(!this.connectionPromise)return t(new Error(`no connection to ${this.url}, can't ping`));try{const r=this.subscribe([{ids:["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],limit:0}],{label:"<forced-ping>",oneose:()=>{e(!0),r.close()},onclose(){e(!0)},eoseTimeout:this.pingTimeout+1e3})}catch(r){t(r)}})}async pingpong(){this.ws?.readyState===1&&(await Promise.any([this.ws&&this.ws.ping&&this.ws.once?this.waitForPingPong():this.waitForDummyReq(),new Promise(t=>setTimeout(()=>t(!1),this.pingTimeout))])||this.ws?.readyState===this._WebSocket.OPEN&&this.ws?.close())}async send(e){if(!this.connectionPromise)throw new mn(e,this.url);this.connectionPromise.then(()=>{this.ws?.send(e)})}async auth(e){const t=this.challenge;if(!t)throw new Error("can't perform auth, no challenge was received");return this.authPromise?this.authPromise:(this.authPromise=new Promise(async(r,n)=>{try{let i=await e(Qr(this.url,t)),s=setTimeout(()=>{let o=this.openEventPublishes.get(i.id);o&&(o.reject(new Error("auth timed out")),this.openEventPublishes.delete(i.id))},this.publishTimeout);this.openEventPublishes.set(i.id,{resolve:r,reject:n,timeout:s}),this.send('["AUTH",'+JSON.stringify(i)+"]")}catch(i){console.warn("subscribe auth function failed:",i)}}),this.authPromise)}async publish(e){this.idleSince=void 0,this.ongoingOperations++;const t=new Promise((r,n)=>{const i=setTimeout(()=>{const s=this.openEventPublishes.get(e.id);s&&(s.reject(new Error("publish timed out")),this.openEventPublishes.delete(e.id))},this.publishTimeout);this.openEventPublishes.set(e.id,{resolve:r,reject:n,timeout:i})});return this.send('["EVENT",'+JSON.stringify(e)+"]"),this.ongoingOperations--,this.ongoingOperations===0&&(this.idleSince=Date.now()),t}async count(e,t){this.serial++;const r=t?.id||"count:"+this.serial,n=new Promise((i,s)=>{this.openCountRequests.set(r,{resolve:i,reject:s})});return this.send('["COUNT","'+r+'",'+JSON.stringify(e).substring(1)),n}subscribe(e,t){t.label!=="<forced-ping>"&&(this.idleSince=void 0,this.ongoingOperations++);const r=this.prepareSubscription(e,t);return r.fire(),t.abort&&(t.abort.onabort=()=>r.close(String(t.abort.reason||"<aborted>"))),r}prepareSubscription(e,t){this.serial++;const r=t.id||(t.label?t.label+":":"sub:")+this.serial,n=new ei(this,r,e,t);return this.openSubs.set(r,n),n}close(){this.skipReconnection=!0,this.reconnectTimeoutHandle&&(clearTimeout(this.reconnectTimeoutHandle),this.reconnectTimeoutHandle=void 0),this.pingIntervalHandle&&(clearInterval(this.pingIntervalHandle),this.pingIntervalHandle=void 0),this.closeAllSubscriptions("relay connection closed by us"),this._connected=!1,this.idleSince=void 0,this.onclose?.(),this.ws?.readyState===this._WebSocket.OPEN&&this.ws?.close()}_onmessage(e){const t=e.data;if(!t)return;const r=Jr(t);if(r){const n=this.openSubs.get(r);if(!n)return;const i=Xr(t,"id"),s=n.alreadyHaveEvent?.(i);if(n.receivedEvent?.(this,i),s)return}try{let n=JSON.parse(t);switch(n[0]){case"EVENT":{const i=this.openSubs.get(n[1]),s=n[2];this.verifyEvent(s)&&Gr(i.filters,s)?i.onevent(s):i.oninvalidevent?.(s),(!i.lastEmitted||i.lastEmitted<s.created_at)&&(i.lastEmitted=s.created_at);return}case"COUNT":{const i=n[1],s=n[2],o=this.openCountRequests.get(i);o&&(o.resolve(s.count),this.openCountRequests.delete(i));return}case"EOSE":{const i=this.openSubs.get(n[1]);if(!i)return;i.receivedEose();return}case"OK":{const i=n[1],s=n[2],o=n[3],c=this.openEventPublishes.get(i);c&&(clearTimeout(c.timeout),s?c.resolve(o):c.reject(new Error(o)),this.openEventPublishes.delete(i));return}case"CLOSED":{const i=n[1],s=this.openSubs.get(i);if(!s)return;s.closed=!0,s.close(n[2]);return}case"NOTICE":{this.onnotice(n[1]);return}case"AUTH":{this.challenge=n[1],this.onauth&&this.auth(this.onauth);return}default:{this.openSubs.get(n[1])?.oncustom?.(n);return}}}catch(n){try{const[i,s,o]=JSON.parse(t);console.warn(`[nostr] relay ${this.url} error processing message:`,n,o)}catch{console.warn(`[nostr] relay ${this.url} error processing message:`,n)}return}}},ei=class{relay;id;lastEmitted;closed=!1;eosed=!1;filters;alreadyHaveEvent;receivedEvent;onevent;oninvalidevent;oneose;onclose;oncustom;eoseTimeout;eoseTimeoutHandle;constructor(e,t,r,n){if(r.length===0)throw new Error("subscription can't be created with zero filters");this.relay=e,this.filters=r,this.id=t,this.alreadyHaveEvent=n.alreadyHaveEvent,this.receivedEvent=n.receivedEvent,this.eoseTimeout=n.eoseTimeout||e.baseEoseTimeout,this.oneose=n.oneose,this.onclose=n.onclose,this.oninvalidevent=n.oninvalidevent,this.onevent=n.onevent||(i=>{console.warn(`onevent() callback not defined for subscription '${this.id}' in relay ${this.relay.url}. event received:`,i)})}fire(){this.relay.send('["REQ","'+this.id+'",'+JSON.stringify(this.filters).substring(1)),this.eoseTimeoutHandle=setTimeout(this.receivedEose.bind(this),this.eoseTimeout)}receivedEose(){this.eosed||(clearTimeout(this.eoseTimeoutHandle),this.eosed=!0,this.oneose?.())}close(e="closed by caller"){if(!this.closed&&this.relay.connected){try{this.relay.send('["CLOSE",'+JSON.stringify(this.id)+"]")}catch(t){if(!(t instanceof mn))throw t}this.closed=!0}this.relay.openSubs.delete(this.id),this.relay.ongoingOperations--,this.relay.ongoingOperations===0&&(this.relay.idleSince=Date.now()),this.onclose?.(e)}},ti=e=>(e[ie]=!0,!0),ni=class{relays=new Map;seenOn=new Map;trackRelays=!1;verifyEvent;enablePing;enableReconnect;automaticallyAuth;trustedRelayURLs=new Set;onRelayConnectionFailure;onRelayConnectionSuccess;allowConnectingToRelay;maxWaitForConnection;_WebSocket;constructor(e){this.verifyEvent=e.verifyEvent,this._WebSocket=e.websocketImplementation,this.enablePing=e.enablePing,this.enableReconnect=e.enableReconnect||!1,this.automaticallyAuth=e.automaticallyAuth,this.onRelayConnectionFailure=e.onRelayConnectionFailure,this.onRelayConnectionSuccess=e.onRelayConnectionSuccess,this.allowConnectingToRelay=e.allowConnectingToRelay,this.maxWaitForConnection=e.maxWaitForConnection||3e3}async ensureRelay(e,t){e=pe(e);let r=this.relays.get(e);if(r||(r=new yn(e,{verifyEvent:this.trustedRelayURLs.has(e)?ti:this.verifyEvent,websocketImplementation:this._WebSocket,enablePing:this.enablePing,enableReconnect:this.enableReconnect}),r.onclose=()=>{this.relays.delete(e)},this.relays.set(e,r)),this.automaticallyAuth){const n=this.automaticallyAuth(e);n&&(r.onauth=n)}try{await r.connect({timeout:t?.connectionTimeout,abort:t?.abort})}catch(n){throw this.relays.delete(e),n}return r}close(e){e.map(pe).forEach(t=>{this.relays.get(t)?.close(),this.relays.delete(t)})}subscribe(e,t,r){const n=[],i=[];for(let s=0;s<e.length;s++){const o=pe(e[s]);n.find(c=>c.url===o)||i.indexOf(o)===-1&&(i.push(o),n.push({url:o,filter:t}))}return this.subscribeMap(n,r)}subscribeMany(e,t,r){return this.subscribe(e,t,r)}subscribeMap(e,t){const r=new Map;for(const g of e){const{url:b,filter:y}=g;r.has(b)||r.set(b,[]),r.get(b).push(y)}const n=Array.from(r.entries()).map(([g,b])=>({url:g,filters:b}));this.trackRelays&&(t.receivedEvent=(g,b)=>{let y=this.seenOn.get(b);y||(y=new Set,this.seenOn.set(b,y)),y.add(g)});const i=new Set,s=[],o=[];let c=g=>{o[g]||(o[g]=!0,o.filter(b=>b).length===n.length&&(t.oneose?.(),c=()=>{}))};const a=[];let l=(g,b)=>{a[g]||(c(g),a[g]=b,a.filter(y=>y).length===n.length&&(t.onclose?.(a),l=()=>{}))};const u=g=>{if(t.alreadyHaveEvent?.(g))return!0;const b=i.has(g);return i.add(g),b},f=Promise.all(n.map(async({url:g,filters:b},y)=>{if(this.allowConnectingToRelay?.(g,["read",b])===!1){l(y,"connection skipped by allowConnectingToRelay");return}let E;try{E=await this.ensureRelay(g,{connectionTimeout:this.maxWaitForConnection<(t.maxWait||0)?Math.max(t.maxWait*.8,t.maxWait-1e3):this.maxWaitForConnection,abort:t.abort})}catch(L){this.onRelayConnectionFailure?.(g),l(y,L?.message||String(L));return}this.onRelayConnectionSuccess?.(g);let N=E.subscribe(b,{...t,oneose:()=>c(y),onclose:L=>{L.startsWith("auth-required: ")&&t.onauth?E.auth(t.onauth).then(()=>{E.subscribe(b,{...t,oneose:()=>c(y),onclose:F=>{l(y,F)},alreadyHaveEvent:u,eoseTimeout:t.maxWait,abort:t.abort})}).catch(F=>{l(y,`auth was required and attempted, but failed with: ${F}`)}):l(y,L)},alreadyHaveEvent:u,eoseTimeout:t.maxWait,abort:t.abort});s.push(N)}));return{async close(g){await f,s.forEach(b=>{b.close(g)})}}}subscribeEose(e,t,r){let n;return n=this.subscribe(e,t,{...r,oneose(){const i="closed automatically on eose";n?n.close(i):r.onclose?.(e.map(s=>i))}}),n}subscribeManyEose(e,t,r){return this.subscribeEose(e,t,r)}async querySync(e,t,r){return new Promise(async n=>{const i=[];this.subscribeEose(e,t,{...r,onevent(s){i.push(s)},onclose(s){n(i)}})})}async get(e,t,r){t.limit=1;const n=await this.querySync(e,t,r);return n.sort((i,s)=>s.created_at-i.created_at),n[0]||null}publish(e,t,r){return e.map(pe).map(async(n,i,s)=>{if(s.indexOf(n)!==i)return Promise.reject("duplicate url");if(this.allowConnectingToRelay?.(n,["write",t])===!1)return Promise.reject("connection skipped by allowConnectingToRelay");let o;try{o=await this.ensureRelay(n,{connectionTimeout:this.maxWaitForConnection<(r?.maxWait||0)?Math.max(r.maxWait*.8,r.maxWait-1e3):this.maxWaitForConnection,abort:r?.abort})}catch(c){return this.onRelayConnectionFailure?.(n),"connection failure: "+String(c)}return o.publish(t).catch(async c=>{if(c instanceof Error&&c.message.startsWith("auth-required: ")&&r?.onauth)return await o.auth(r.onauth),o.publish(t);throw c}).then(c=>{if(this.trackRelays){let a=this.seenOn.get(t.id);a||(a=new Set,this.seenOn.set(t.id,a)),a.add(o)}return c})})}listConnectionStatus(){const e=new Map;return this.relays.forEach((t,r)=>e.set(r,t.connected)),e}destroy(){this.relays.forEach(e=>e.close()),this.relays=new Map}pruneIdleRelays(e=1e4){const t=[];for(const[r,n]of this.relays)n.idleSince&&Date.now()-n.idleSince>=e&&(this.relays.delete(r),t.push(r),n.close());return t}},wn;try{wn=WebSocket}catch{}var ri=class extends ni{constructor(e){super({verifyEvent:Fr,websocketImplementation:wn,maxWaitForConnection:3e3,...e})}},ce=Symbol("verified"),ii=e=>e instanceof Object;function si(e){if(!ii(e)||typeof e.kind!="number"||typeof e.content!="string"||typeof e.created_at!="number"||typeof e.pubkey!="string"||!e.pubkey.match(/^[a-f0-9]{64}$/)||!Array.isArray(e.tags))return!1;for(let t=0;t<e.tags.length;t++){let r=e.tags[t];if(!Array.isArray(r))return!1;for(let n=0;n<r.length;n++)if(typeof r[n]!="string")return!1}return!0}new TextDecoder("utf-8");var oi=new TextEncoder,ai=class{generateSecretKey(){return G.utils.randomSecretKey()}getPublicKey(e){return _(G.getPublicKey(e))}finalizeEvent(e,t){const r=e;return r.pubkey=_(G.getPublicKey(t)),r.id=Ke(r),r.sig=_(G.sign(X(Ke(r)),t)),r[ce]=!0,r}verifyEvent(e){if(typeof e[ce]=="boolean")return e[ce];try{const t=Ke(e);if(t!==e.id)return e[ce]=!1,!1;const r=G.verify(X(e.sig),X(t),X(e.pubkey));return e[ce]=r,r}catch{return e[ce]=!1,!1}}};function ci(e){if(!si(e))throw new Error("can't serialize event with wrong or missing properties");return JSON.stringify([0,e.pubkey,e.created_at,e.kind,e.tags,e.content])}function Ke(e){let t=Ae(oi.encode(ci(e)));return _(t)}var Ce=new ai;Ce.generateSecretKey;Ce.getPublicKey;Ce.finalizeEvent;var li=Ce.verifyEvent;const di=8e3;function ui(e){return typeof e=="number"&&st.includes(e)}function fi(e){try{const t=new URL(e);return t.protocol==="http:"||t.protocol==="https:"}catch{return!1}}function hi(e){const t=[];for(const r of e??[])try{const n=new URL(r);if(n.protocol!=="ws:"&&n.protocol!=="wss:")continue;t.push(n.href)}catch{}return[...new Set(t)]}function pi(e){const t=rr(e.trim());if(t.type!=="nevent"&&t.type!=="naddr")throw new Error("Enter a NIP-19 nevent or naddr for a NIP-5D napplet manifest event");const r=hi(t.data.relays);if(r.length===0)throw new Error("The pointer does not include relay hints; include at least one relay in the nevent/naddr");if(t.type==="nevent"){if(t.data.kind!==void 0&&!ui(t.data.kind))throw new Error(`nevent points at kind ${t.data.kind}, not a NIP-5D napplet manifest kind`);return{type:"nevent",relays:r,filter:{ids:[t.data.id],kinds:t.data.kind===void 0?[...st]:[t.data.kind],authors:t.data.author?[t.data.author]:void 0,limit:1}}}if(t.data.kind!==ge)throw new Error(`naddr kind ${t.data.kind} is not the named napplet kind ${ge}`);return{type:"naddr",relays:r,filter:{kinds:[ge],authors:[t.data.pubkey],"#d":[t.data.identifier],limit:1}}}function gi(e){return{id:e.id,pubkey:e.pubkey,kind:e.kind,tags:e.tags}}async function vn(e){const t=typeof e=="string"?new TextEncoder().encode(e):e,r=new Uint8Array(t.byteLength);r.set(t);const n=await crypto.subtle.digest("SHA-256",r.buffer);return[...new Uint8Array(n)].map(i=>i.toString(16).padStart(2,"0")).join("")}function bi(e){return e.tags.filter(t=>t[0]==="path"&&typeof t[1]=="string"&&typeof t[2]=="string")}async function mi(e){const t=bi(e).map(r=>`${r[2]} ${r[1]}
|
|
77
|
+
`).sort().join("");return vn(t)}async function yi(e){const t=await mi(e);for(const r of e.tags.filter(n=>n[0]==="x"))if(r[2]!==t)throw new Error(`Manifest x tag does not match recomputed aggregate hash ${t}`)}function wi(e){return e.tags.find(t=>t[0]==="path"&&t[1]==="/index.html")}function vi(e){const t=[];for(const r of e.tags)if(!(r[0]!=="server"||!r[1]))try{const n=new URL(r[1]);(n.protocol==="http:"||n.protocol==="https:")&&t.push(n.href)}catch{}return[...new Set(t)]}function Ei(e,t){const r=e.endsWith("/")?e:`${e}/`;return new URL(t,r).href}async function xi(e,t=fetch){const n=wi(e)?.[2];if(!n)throw new Error("Manifest does not include a /index.html path hash");const i=vi(e);if(i.length===0)throw new Error("Manifest does not include any usable Blossom server hints");const s=[];for(const o of i){const c=Ei(o,n);try{const a=await t(c,{mode:"cors"});if(!a.ok){s.push(`${c}: HTTP ${a.status}`);continue}const l=new Uint8Array(await a.arrayBuffer()),u=await vn(l);if(u!==n){s.push(`${c}: sha256 ${u} did not match manifest ${n}`);continue}return new TextDecoder().decode(l)}catch(a){s.push(`${c}: ${a instanceof Error?a.message:String(a)}`)}}throw new Error(`Unable to fetch verified /index.html from Blossom servers: ${s.join("; ")}`)}async function ki(e,t){try{const n=await(await t(e,{mode:"cors"})).text();return{source:"url",input:e,label:e,bootUrl:e,html:n,fetched:!0,manifestEvent:null}}catch{return{source:"url",input:e,label:e,bootUrl:e,html:"",fetched:!1,manifestEvent:null}}}async function Si(e,t){const r=pi(e),n=!t.pool,i=t.pool??new ri;try{const s=await i.get(r.relays,{...r.filter},{maxWait:t.maxRelayWaitMs??di});if(!s)throw new Error("No matching napplet manifest event was found on the pointer relay hints");if(!li(s))throw new Error("Resolved manifest event signature is invalid");const o=gi(s),c=ot(o);if(!c.ok)throw new Error(`Resolved event is not a valid NIP-5D napplet manifest: ${c.errors[0]?.message}`);await yi(o);const a=await xi(o,t.fetcher??fetch),l=(t.createObjectUrl??URL.createObjectURL)(new Blob([a],{type:"text/html;charset=utf-8"})),u=()=>(t.revokeObjectUrl??URL.revokeObjectURL)(l);return{source:"nostr",input:e,label:o.id??e,bootUrl:l,html:a,fetched:!0,manifestEvent:o,revoke:u}}finally{n&&i.destroy()}}async function Ri(e,t={}){const r=e.trim();if(!r)throw new Error("Enter a NIP-19 nevent/naddr, or an HTTP URL for local dev");return fi(r)?ki(r,t.fetcher??fetch):Si(r,t)}const $i=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,Ti="/__conformance__/events",nt=new URLSearchParams(location.search),En=nt.get("live")==="1";function ne(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const re=document.getElementById("app");function Bi(){return re.innerHTML=`
|
|
78
|
+
<header class="masthead">
|
|
79
|
+
<h1>napplet conformance ${En?'<span class="live-badge" id="live-badge">● live</span>':""}</h1>
|
|
80
|
+
<p>Load a napplet by <code>nevent</code> or <code>naddr</code> and run NAP protocol conformance live. Same engine as <code>napplet-conformance</code> CI.</p>
|
|
81
|
+
</header>
|
|
82
|
+
<form id="run-form" class="runbar">
|
|
83
|
+
<input id="target" type="text" placeholder="naddr1… or nevent1… for a NIP-5D napplet manifest" autocomplete="off" spellcheck="false" />
|
|
84
|
+
<button id="run" type="submit">Run conformance</button>
|
|
85
|
+
<button id="rerun" type="button" class="secondary" hidden>Re-run</button>
|
|
86
|
+
</form>
|
|
87
|
+
<p id="status" class="status" role="status"></p>
|
|
88
|
+
<section id="output" class="output"></section>
|
|
89
|
+
`,{targetInput:re.querySelector("#target"),runButton:re.querySelector("#run"),rerunButton:re.querySelector("#rerun"),liveBadge:re.querySelector("#live-badge")??document.createElement("span"),status:re.querySelector("#status"),output:re.querySelector("#output")}}const C=Bi();let ye="",rt=0;function Re(e,t="info"){C.status.textContent=e,C.status.dataset.kind=t}function Fe(e){return String(e).padStart(2,"0")}function Ii(e){const t=new Date(e);return`${Fe(t.getHours())}:${Fe(t.getMinutes())}:${Fe(t.getSeconds())}`}function Ai(e){const t=e.ok?"conformant":"non-conformant",n=[...new Set(e.checks.map(o=>o.area))].map(o=>{const c=e.checks.filter(a=>a.area===o).map(a=>{const l=a.severity==="warning"&&a.status==="fail"?' <span class="tag warn">warning</span>':"",u=a.detail?`<span class="detail">${ne(a.detail)}</span>`:"";return`<li class="check ${a.status}"><span class="badge ${a.status}">${a.status}</span><span class="id">${ne(a.id)}</span>${l}${u}</li>`}).join("");return`<div class="area"><h3>${ne(o)}</h3><ul>${c}</ul></div>`}).join(""),i=e.summary,s=rt>1?`<span class="run-stamp">run #${rt} · ${Ii(e.finishedAt)}</span>`:"";return`
|
|
90
|
+
<div class="verdict ${t}">
|
|
91
|
+
<span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
|
|
92
|
+
<span class="counts">${i.passed} passed · ${i.failed} failed · ${i.skipped} skipped${i.warnings?` · ${i.warnings} warning(s)`:""}</span>
|
|
93
|
+
${s}
|
|
94
|
+
</div>
|
|
95
|
+
<div class="checks">${n}</div>
|
|
96
|
+
`}function Oi(e){return e.length===0?'<p class="empty">No envelopes were emitted.</p>':`<ul class="envlog">${e.map(r=>{const n=r.verdict.ok?"ok":"bad",i=r.verdict.ok?"":`<div class="errs">${r.verdict.errors.map(s=>ne(`${s.code}: ${s.message}`)).join("<br>")}</div>`;return`<li class="env ${n}"><code>${ne(JSON.stringify(r.envelope))}</code>${i}</li>`}).join("")}</ul>`}function Ni(e){const t=(r,n)=>`<tr><th>${ne(r)}</th><td>${n?ne(String(n)):"<em>—</em>"}</td></tr>`;if(e.manifestEvent){const r=e.manifestEvent,n=ot(r),i=r.tags.find(c=>c[0]==="d")?.[1],s=r.tags.find(c=>c[0]==="path"&&c[1]==="/index.html"),o=r.tags.filter(c=>c[0]==="server").map(c=>c[1]).filter(Boolean);return`
|
|
97
|
+
<table class="manifest">
|
|
98
|
+
${t("event",r.id)}
|
|
99
|
+
${t("kind",r.kind)}
|
|
100
|
+
${t("d",i)}
|
|
101
|
+
${t("name",Mt(r))}
|
|
102
|
+
${t("requires",n.requires.join(", "))}
|
|
103
|
+
${t("/index.html",s?.[2])}
|
|
104
|
+
${t("servers",o.join(", "))}
|
|
105
|
+
</table>
|
|
106
|
+
${n.errors.length?`<div class="errs">${n.errors.map(c=>ne(`${c.code}: ${c.message}`)).join("<br>")}</div>`:""}
|
|
107
|
+
`}return e.fetched?'<p class="empty">Local URL mode booted raw HTML. It does not include a signed NIP-5D manifest event, so manifest-event checks are skipped.</p>':`<p class="empty">Couldn't fetch the local URL HTML, likely because of CORS. Use a NIP-19 <code>nevent</code>/<code>naddr</code> for authoritative manifest checks, or use the CLI for local URL runs.</p>`}function qi(e,t,r){C.output.innerHTML=`
|
|
108
|
+
${Ai(e)}
|
|
109
|
+
<div class="panels">
|
|
110
|
+
<section class="panel"><h2>Emitted envelopes (${t.length})</h2>${Oi(t)}</section>
|
|
111
|
+
<section class="panel"><h2>Manifest</h2>${Ni(r)}</section>
|
|
112
|
+
</div>
|
|
113
|
+
`}async function Ue(e){ye=e,C.runButton.disabled=!0,C.rerunButton.disabled=!0;let t=null;try{Re(`Resolving ${e} …`),t=await Ri(e);const r=$i.test(t.html)?["window.nostr"]:[];Re("Booting napplet in a sandboxed iframe …");const n=await Un({url:t.bootUrl,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),i=Nn({manifestHtml:t.html,manifestEvent:t.manifestEvent,boot:n,forbiddenGlobals:r}),s=Wn(i);rt+=1,Re(s.ok?"Done — conformant.":"Done — non-conformant.",s.ok?"info":"error"),qi(s,n.emitted,t),C.rerunButton.hidden=!1}catch(r){Re(`Error: ${r instanceof Error?r.message:String(r)}`,"error")}finally{t?.revoke?.(),C.runButton.disabled=!1,C.rerunButton.disabled=!1}}function Li(){if(!En||typeof EventSource>"u")return;let e=!1;const t=new EventSource(Ti);t.addEventListener("rerun",()=>{!ye||e||(e=!0,setTimeout(()=>{e=!1,Ue(ye)},50))}),t.addEventListener("open",()=>{C.liveBadge&&(C.liveBadge.dataset.state="connected")}),t.addEventListener("error",()=>{C.liveBadge&&(C.liveBadge.dataset.state="disconnected")})}C.runButton.parentElement.addEventListener("submit",e=>{e.preventDefault();const t=C.targetInput.value.trim();if(t){const r=new URL(location.href);r.searchParams.set("target",t),r.searchParams.delete("url"),history.replaceState(null,"",r),Ue(t)}});C.rerunButton.addEventListener("click",()=>{ye&&Ue(ye)});Li();const Ye=nt.get("target")??nt.get("url");Ye&&(C.targetInput.value=Ye,Ue(Ye));
|
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-j_MRjiMC.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.15",
|
|
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,14 +15,14 @@
|
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"playwright": "^1.59.1",
|
|
18
|
-
"@napplet/conformance": "^0.
|
|
18
|
+
"@napplet/conformance": "^0.13.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
24
|
"vitest": "^4.1.2",
|
|
25
|
-
"@napplet/conformance-web": "0.0.
|
|
25
|
+
"@napplet/conformance-web": "0.0.14"
|
|
26
26
|
},
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"repository": {
|
|
@@ -1,107 +0,0 @@
|
|
|
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"]'))d(s);new MutationObserver(s=>{for(const o of s)if(o.type==="childList")for(const a of o.addedNodes)a.tagName==="LINK"&&a.rel==="modulepreload"&&d(a)}).observe(document,{childList:!0,subtree:!0});function i(s){const o={};return s.integrity&&(o.integrity=s.integrity),s.referrerPolicy&&(o.referrerPolicy=s.referrerPolicy),s.crossOrigin==="use-credentials"?o.credentials="include":s.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function d(s){if(s.ep)return;s.ep=!0;const o=i(s);fetch(s.href,o)}})();var R=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent","ble","webrtc","link","count","lists","serial","common","dm"],r={id:"string"},F={"relay.subscribe":{dir:"out",fields:{...r,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...r,subId:"string"}},"relay.publish":{dir:"out",fields:{...r,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...r,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...r,filters:"array"}},"relay.event":{dir:"in"},"relay.eose":{dir:"in"},"relay.closed":{dir:"in"},"relay.publish.result":{dir:"in"},"relay.publishEncrypted.result":{dir:"in"},"relay.query.result":{dir:"in"},"identity.getPublicKey":{dir:"out",fields:{...r}},"identity.getRelays":{dir:"out",fields:{...r}},"identity.getProfile":{dir:"out",fields:{...r}},"identity.getFollows":{dir:"out",fields:{...r}},"identity.getList":{dir:"out",fields:{...r,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...r}},"identity.getMutes":{dir:"out",fields:{...r}},"identity.getBlocked":{dir:"out",fields:{...r}},"identity.getBadges":{dir:"out",fields:{...r}},"identity.changed":{dir:"in"},"identity.getPublicKey.result":{dir:"in"},"identity.getRelays.result":{dir:"in"},"identity.getProfile.result":{dir:"in"},"identity.getFollows.result":{dir:"in"},"identity.getList.result":{dir:"in"},"identity.getZaps.result":{dir:"in"},"identity.getMutes.result":{dir:"in"},"identity.getBlocked.result":{dir:"in"},"identity.getBadges.result":{dir:"in"},"storage.get":{dir:"out",fields:{...r,key:"string"}},"storage.set":{dir:"out",fields:{...r,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...r,key:"string"}},"storage.keys":{dir:"out",fields:{...r}},"storage.get.result":{dir:"in"},"storage.set.result":{dir:"in"},"storage.remove.result":{dir:"in"},"storage.keys.result":{dir:"in"},"inc.emit":{dir:"out",fields:{topic:"string"}},"inc.subscribe":{dir:"out",fields:{...r,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...r,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...r}},"inc.channel.close":{dir:"out",fields:{channelId:"string"}},"inc.subscribe.result":{dir:"in"},"inc.event":{dir:"in"},"inc.channel.open.result":{dir:"in"},"inc.channel.event":{dir:"in"},"inc.channel.list.result":{dir:"in"},"inc.channel.closed":{dir:"in"},"theme.get":{dir:"out",fields:{...r}},"theme.get.result":{dir:"in"},"theme.changed":{dir:"in"},"keys.forward":{dir:"out",fields:{key:"string",code:"string",ctrl:"boolean",alt:"boolean",shift:"boolean",meta:"boolean"}},"keys.registerAction":{dir:"out",fields:{...r,action:"object"}},"keys.unregisterAction":{dir:"out",fields:{actionId:"string"}},"keys.registerAction.result":{dir:"in"},"keys.bindings":{dir:"in"},"keys.action":{dir:"in"},"media.session.create":{dir:"out",fields:{...r,owner:"object"}},"media.session.update":{dir:"out",fields:{sessionId:"string",metadata:"object"}},"media.session.destroy":{dir:"out",fields:{sessionId:"string"}},"media.state":{dir:"out",fields:{sessionId:"string",status:"string"}},"media.capabilities":{dir:"out",fields:{sessionId:"string",actions:"array"}},"media.session.create.result":{dir:"in"},"media.command":{dir:"in"},"media.controls":{dir:"in"},"notify.send":{dir:"out",fields:{...r,title:"string"}},"notify.dismiss":{dir:"out",fields:{notificationId:"string"}},"notify.badge":{dir:"out",fields:{count:"number"}},"notify.channel.register":{dir:"out",fields:{channelId:"string",label:"string"}},"notify.permission.request":{dir:"out",fields:{...r}},"notify.send.result":{dir:"in"},"notify.permission.result":{dir:"in"},"notify.action":{dir:"in"},"notify.clicked":{dir:"in"},"notify.dismissed":{dir:"in"},"notify.controls":{dir:"in"},"config.registerSchema":{dir:"out",fields:{...r,schema:"object"}},"config.get":{dir:"out",fields:{...r}},"config.subscribe":{dir:"out"},"config.unsubscribe":{dir:"out"},"config.openSettings":{dir:"out"},"config.registerSchema.result":{dir:"in"},"config.values":{dir:"in"},"config.schemaError":{dir:"in"},"resource.info":{dir:"out",fields:{...r}},"resource.bytes":{dir:"out",fields:{...r,url:"string"}},"resource.bytesMany":{dir:"out",fields:{...r,urls:"array"}},"resource.cancel":{dir:"out",fields:{...r}},"resource.info.result":{dir:"in"},"resource.info.error":{dir:"in"},"resource.bytes.result":{dir:"in"},"resource.bytes.error":{dir:"in"},"resource.bytesMany.result":{dir:"in"},"resource.bytesMany.error":{dir:"in"},"cvm.discover":{dir:"out",fields:{...r}},"cvm.request":{dir:"out",fields:{...r,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...r,server:"object"}},"cvm.registry.list":{dir:"out",fields:{...r}},"cvm.registry.has":{dir:"out",fields:{...r,family:"string"}},"cvm.registry.describe":{dir:"out",fields:{...r,family:"string"}},"cvm.registry.call":{dir:"out",fields:{...r,family:"string",tool:"string"}},"cvm.discover.result":{dir:"in"},"cvm.request.result":{dir:"in"},"cvm.close.result":{dir:"in"},"cvm.event":{dir:"in"},"cvm.registry.list.result":{dir:"in"},"cvm.registry.has.result":{dir:"in"},"cvm.registry.describe.result":{dir:"in"},"cvm.registry.call.result":{dir:"in"},"outbox.getEvent":{dir:"out",fields:{...r,eventId:"string"}},"outbox.query":{dir:"out",fields:{...r,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...r,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...r,subId:"string"}},"outbox.publish":{dir:"out",fields:{...r,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...r,target:"present"}},"outbox.getEvent.result":{dir:"in"},"outbox.query.result":{dir:"in"},"outbox.event":{dir:"in"},"outbox.closed":{dir:"in"},"outbox.publish.result":{dir:"in"},"outbox.resolveRelays.result":{dir:"in"},"upload.info":{dir:"out",fields:{...r}},"upload.upload":{dir:"out",fields:{...r,request:"object"}},"upload.status":{dir:"out",fields:{...r,uploadId:"string"}},"upload.info.result":{dir:"in"},"upload.upload.result":{dir:"in"},"upload.status.result":{dir:"in"},"upload.status.changed":{dir:"in"},"intent.invoke":{dir:"out",fields:{...r,request:"object"}},"intent.available":{dir:"out",fields:{...r,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...r}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"},"ble.open":{dir:"out",fields:{...r,request:"object"}},"ble.services":{dir:"out",fields:{...r,sessionId:"string"}},"ble.read":{dir:"out",fields:{...r,sessionId:"string",target:"object"}},"ble.write":{dir:"out",fields:{...r,sessionId:"string",target:"object",data:"array"}},"ble.subscribe":{dir:"out",fields:{...r,sessionId:"string",target:"object"}},"ble.unsubscribe":{dir:"out",fields:{...r,sessionId:"string",target:"object"}},"ble.close":{dir:"out",fields:{...r,sessionId:"string"}},"ble.open.result":{dir:"in"},"ble.services.result":{dir:"in"},"ble.read.result":{dir:"in"},"ble.write.result":{dir:"in"},"ble.subscribe.result":{dir:"in"},"ble.unsubscribe.result":{dir:"in"},"ble.close.result":{dir:"in"},"ble.event":{dir:"in"},"common.encodeNip19":{dir:"out",fields:{...r,input:"object"}},"common.decodeNip19":{dir:"out",fields:{...r,value:"string"}},"common.getProfile":{dir:"out",fields:{...r,target:"string"}},"common.follows":{dir:"out",fields:{...r}},"common.follow":{dir:"out",fields:{...r,pubkeys:"array"}},"common.unfollow":{dir:"out",fields:{...r,pubkeys:"array"}},"common.react":{dir:"out",fields:{...r,targetEventId:"string",reaction:"string"}},"common.report":{dir:"out",fields:{...r,target:"object",reason:"string",text:"string"}},"common.encodeNip19.result":{dir:"in"},"common.decodeNip19.result":{dir:"in"},"common.getProfile.result":{dir:"in"},"common.follows.result":{dir:"in"},"common.follow.result":{dir:"in"},"common.unfollow.result":{dir:"in"},"common.react.result":{dir:"in"},"common.report.result":{dir:"in"},"webrtc.open":{dir:"out",fields:{...r,request:"object"}},"webrtc.send":{dir:"out",fields:{...r,sessionId:"string",payload:"present"}},"webrtc.close":{dir:"out",fields:{...r,sessionId:"string"}},"webrtc.open.result":{dir:"in"},"webrtc.send.result":{dir:"in"},"webrtc.close.result":{dir:"in"},"webrtc.event":{dir:"in"},"link.open":{dir:"out",fields:{...r,url:"string"}},"link.open.result":{dir:"in"},"count.query":{dir:"out",fields:{...r,filters:"array"}},"count.query.result":{dir:"in"},"lists.supported":{dir:"out",fields:{...r}},"lists.add":{dir:"out",fields:{...r,list:"object",items:"array"}},"lists.remove":{dir:"out",fields:{...r,list:"object",items:"array"}},"lists.supported.result":{dir:"in"},"lists.add.result":{dir:"in"},"lists.remove.result":{dir:"in"},"serial.open":{dir:"out",fields:{...r,request:"object"}},"serial.write":{dir:"out",fields:{...r,sessionId:"string",data:"array"}},"serial.close":{dir:"out",fields:{...r,sessionId:"string"}},"serial.open.result":{dir:"in"},"serial.write.result":{dir:"in"},"serial.close.result":{dir:"in"},"serial.event":{dir:"in"},"dm.status":{dir:"out",fields:{...r}},"dm.conversations":{dir:"out",fields:{...r}},"dm.messages":{dir:"out",fields:{...r,conversationId:"string"}},"dm.send":{dir:"out",fields:{...r,recipients:"array",content:"string"}},"dm.subscribe":{dir:"out",fields:{...r}},"dm.unsubscribe":{dir:"out",fields:{...r,subscriptionId:"string"}},"dm.status.result":{dir:"in"},"dm.conversations.result":{dir:"in"},"dm.messages.result":{dir:"in"},"dm.send.result":{dir:"in"},"dm.subscribe.result":{dir:"in"},"dm.unsubscribe.result":{dir:"in"},"dm.message":{dir:"in"}};function 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 G(e,t){return t==="present"?e!=null:M(e)===t}function K(e){const t=[];if(typeof e!="object"||e===null||Array.isArray(e))return{ok:!1,errors:[{code:"not-an-object",message:"Envelope must be a non-null object"}]};const i=e,d=i.type;if(typeof d!="string")return{ok:!1,errors:[{code:"missing-type",message:"Envelope is missing a string `type` field"}]};const s=d.indexOf(".");if(s<=0)return{ok:!1,type:d,errors:[{code:"malformed-type",message:`Envelope type "${d}" is not in domain.action form`}]};const o=d.slice(0,s);if(!R.includes(o))return{ok:!1,type:d,domain:o,errors:[{code:"unknown-domain",message:`"${o}" is not a known NAP domain`}]};const c=F[d];if(!c)return{ok:!1,type:d,domain:o,errors:[{code:"unknown-type",message:`"${d}" is not a known ${o} message type`}]};if(c.dir==="in")return{ok:!1,type:d,domain:o,direction:"in",errors:[{code:"inbound-type-emitted",message:`"${d}" is a shell→napplet message; a napplet must not emit it`}]};for(const[l,y]of Object.entries(c.fields??{})){if(!(l in i)||i[l]===void 0){t.push({code:"missing-field",message:`Required field "${l}" is missing`,field:l});continue}G(i[l],y)||t.push({code:"wrong-type",message:`Field "${l}" should be ${y} but is ${M(i[l])}`,field:l})}return{ok:t.length===0,type:d,domain:o,direction:"out",errors:t}}var x=/^[a-z0-9][a-z0-9._:-]*$/;function W(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 S(e,t){const i=/<meta\b[^>]*>/gi;let d;for(;(d=i.exec(e))!==null;){const s=d[0],o=/\bname\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(s);if((o?.[1]??o?.[2]??"").trim()!==t)continue;const c=/\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i.exec(s);return W((c?.[1]??c?.[2]??"").trim())}}function N(e){if(Array.isArray(e))return e.some(N);if(e&&typeof e=="object"){const t=e;return"pattern"in t?!0:Object.values(t).some(N)}return!1}function $(e,t={}){const i=[],d=[],s=S(e,"napplet-type");s?x.test(s)||i.push({code:"invalid-napplet-type",message:`napplet-type "${s}" must match ${x}`}):i.push({code:"missing-napplet-type",message:'No <meta name="napplet-type"> declared'});const o=S(e,"napplet-requires"),a=o?o.split(",").map(l=>l.trim()).filter(Boolean):[];for(const l of a){const y=l.startsWith("nap:")?l.slice(4):l;R.includes(y)||i.push({code:"unknown-required-nap",message:`napplet-requires lists "${l}" which is not a known NAP domain`})}const c=S(e,"napplet-config-schema");if(c){let l;try{l=JSON.parse(c)}catch{l=void 0,i.push({code:"invalid-config-schema",message:"napplet-config-schema is not valid JSON"})}l!==void 0&&(typeof l!="object"||l===null||Array.isArray(l)?i.push({code:"invalid-config-schema",message:"napplet-config-schema must be a JSON Schema object"}):N(l)&&i.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:i.length===0,nappletType:s,requires:a,errors:i,warnings:d}}var w="f".repeat(64),J="https://reference.invalid/blob",n=e=>[e],p=()=>[];function O(e){if(typeof e!="string"||!e.startsWith("data:"))return null;const t=e.indexOf(",");if(t<0)return{blob:new Blob([],{type:"text/plain"}),mime:"text/plain"};const i=e.slice(5,t),d=e.slice(t+1),s=i.split(";").filter(Boolean),o=s.includes("base64"),a=s.find(c=>c.includes("/"))??"text/plain";try{if(o){const c=globalThis.atob(d),l=Uint8Array.from(c,y=>y.charCodeAt(0));return{blob:new Blob([l],{type:a}),mime:a}}return{blob:new Blob([decodeURIComponent(d)],{type:a}),mime:a}}catch{return null}}var Z={"relay.subscribe":e=>n({type:"relay.eose",subId:e.subId}),"relay.close":p,"relay.publish":e=>n({type:"relay.publish.result",id:e.id,ok:!0,event:e.event}),"relay.publishEncrypted":e=>n({type:"relay.publishEncrypted.result",id:e.id,ok:!0,event:e.event}),"relay.query":e=>n({type:"relay.query.result",id:e.id,events:[]}),"identity.getPublicKey":e=>n({type:"identity.getPublicKey.result",id:e.id,pubkey:w}),"identity.getRelays":e=>n({type:"identity.getRelays.result",id:e.id,relays:{}}),"identity.getProfile":e=>n({type:"identity.getProfile.result",id:e.id,profile:null}),"identity.getFollows":e=>n({type:"identity.getFollows.result",id:e.id,pubkeys:[]}),"identity.getList":e=>n({type:"identity.getList.result",id:e.id,entries:[]}),"identity.getZaps":e=>n({type:"identity.getZaps.result",id:e.id,zaps:[]}),"identity.getMutes":e=>n({type:"identity.getMutes.result",id:e.id,pubkeys:[]}),"identity.getBlocked":e=>n({type:"identity.getBlocked.result",id:e.id,pubkeys:[]}),"identity.getBadges":e=>n({type:"identity.getBadges.result",id:e.id,badges:[]}),"storage.get":e=>n({type:"storage.get.result",id:e.id,value:null}),"storage.set":e=>n({type:"storage.set.result",id:e.id}),"storage.remove":e=>n({type:"storage.remove.result",id:e.id}),"storage.keys":e=>n({type:"storage.keys.result",id:e.id,keys:[]}),"inc.emit":p,"inc.subscribe":e=>n({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":p,"inc.channel.open":e=>n({type:"inc.channel.open.result",id:e.id,channelId:`chan-${String(e.id)}`,peer:"reference-peer"}),"inc.channel.emit":p,"inc.channel.broadcast":p,"inc.channel.list":e=>n({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":p,"theme.get":e=>n({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":p,"keys.registerAction":e=>n({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":p,"media.session.create":e=>n({type:"media.session.create.result",id:e.id,sessionId:`session-${String(e.id)}`,owner:e.owner}),"media.session.update":p,"media.session.destroy":p,"media.state":p,"media.capabilities":p,"notify.send":e=>n({type:"notify.send.result",id:e.id,notificationId:`notif-${String(e.id)}`}),"notify.dismiss":p,"notify.badge":p,"notify.channel.register":p,"notify.permission.request":e=>n({type:"notify.permission.result",id:e.id,granted:!0}),"config.registerSchema":e=>n({type:"config.registerSchema.result",id:e.id,ok:!0}),"config.get":e=>n({type:"config.values",id:e.id,values:{}}),"config.subscribe":()=>n({type:"config.values",values:{}}),"config.unsubscribe":p,"config.openSettings":p,"resource.bytes":e=>{const t=O(e.url);return n(t?{type:"resource.bytes.result",id:e.id,blob:t.blob,mime:t.mime}:{type:"resource.bytes.result",id:e.id,blob:new Blob([]),mime:"application/octet-stream"})},"resource.bytesMany":e=>n({type:"resource.bytesMany.result",id:e.id,items:Array.isArray(e.urls)?e.urls.map(t=>{const i=O(t);return{url:t,ok:!0,blob:i?.blob??new Blob([]),mime:i?.mime??"application/octet-stream"}}):[]}),"resource.info":e=>n({type:"resource.info.result",id:e.id,info:{schemes:[{scheme:"data",enabled:!0},{scheme:"https",enabled:!0}]}}),"resource.cancel":p,"cvm.discover":e=>n({type:"cvm.discover.result",id:e.id,servers:[]}),"cvm.request":e=>n({type:"cvm.request.result",id:e.id,message:{}}),"cvm.close":e=>n({type:"cvm.close.result",id:e.id}),"outbox.getEvent":e=>n({type:"outbox.getEvent.result",id:e.id}),"outbox.query":e=>n({type:"outbox.query.result",id:e.id,events:[]}),"outbox.subscribe":e=>n({type:"outbox.closed",subId:e.subId,reason:"reference shell complete"}),"outbox.close":p,"outbox.publish":e=>n({type:"outbox.publish.result",id:e.id,ok:!0}),"outbox.resolveRelays":e=>n({type:"outbox.resolveRelays.result",id:e.id,plan:{}}),"upload.info":e=>n({type:"upload.info.result",id:e.id,info:{rails:[{rail:"nip96",enabled:!0,returns:["https"]},{rail:"blossom",enabled:!0,returns:["https","blossom"]}]}}),"upload.upload":e=>n({type:"upload.upload.result",id:e.id,result:{url:J}}),"upload.status":e=>n({type:"upload.status.result",id:e.id,status:{}}),"intent.invoke":e=>n({type:"intent.invoke.result",id:e.id,result:{ok:!0,archetype:"reference",action:"open",handled:!0}}),"intent.available":e=>n({type:"intent.available.result",id:e.id,availability:{archetype:e.archetype,available:!0,candidates:[{dTag:"reference-handler",actions:["open"],protocols:["NAP-4"],contracts:[{action:"open",protocol:"NAP-4",eventKinds:[1]}],isDefault:!0}],hasDefault:!0}}),"intent.handlers":e=>n({type:"intent.handlers.result",id:e.id,handlers:[]}),"ble.open":e=>n({type:"ble.open.result",id:e.id,session:{id:"ble-reference",state:"open",device:{id:"reference-device",name:"Reference BLE"}}}),"ble.services":e=>n({type:"ble.services.result",id:e.id,services:[]}),"ble.read":e=>n({type:"ble.read.result",id:e.id,data:[]}),"ble.write":e=>n({type:"ble.write.result",id:e.id}),"ble.subscribe":e=>n({type:"ble.subscribe.result",id:e.id}),"ble.unsubscribe":e=>n({type:"ble.unsubscribe.result",id:e.id}),"ble.close":e=>n({type:"ble.close.result",id:e.id}),"common.encodeNip19":e=>n({type:"common.encodeNip19.result",id:e.id,ok:!0,value:"npub1reference",nip19Type:"npub"}),"common.decodeNip19":e=>n({type:"common.decodeNip19.result",id:e.id,ok:!0,nip19Type:"npub",hex:w}),"common.getProfile":e=>n({type:"common.getProfile.result",id:e.id,ok:!0,pubkey:w,profile:null}),"common.follows":e=>n({type:"common.follows.result",id:e.id,ok:!0,pubkeys:[]}),"common.follow":e=>n({type:"common.follow.result",id:e.id,ok:!0}),"common.unfollow":e=>n({type:"common.unfollow.result",id:e.id,ok:!0}),"common.react":e=>n({type:"common.react.result",id:e.id,ok:!0,eventId:"0".repeat(64)}),"common.report":e=>n({type:"common.report.result",id:e.id,ok:!0,eventId:"1".repeat(64)}),"webrtc.open":e=>n({type:"webrtc.open.result",id:e.id,session:{id:"webrtc-reference",scope:{type:"direct",pubkey:w},channel:"default",state:"connecting"}}),"webrtc.send":e=>n({type:"webrtc.send.result",id:e.id}),"webrtc.close":e=>n({type:"webrtc.close.result",id:e.id}),"link.open":e=>n({type:"link.open.result",id:e.id,status:"opened"}),"count.query":e=>n({type:"count.query.result",id:e.id,ok:!0,count:0}),"lists.supported":e=>n({type:"lists.supported.result",id:e.id,lists:[]}),"lists.add":e=>n({type:"lists.add.result",id:e.id,ok:!0,added:0,skipped:0}),"lists.remove":e=>n({type:"lists.remove.result",id:e.id,ok:!0,removed:0,skipped:0}),"serial.open":e=>n({type:"serial.open.result",id:e.id,session:{id:`serial-${String(e.id)}`,state:"open"}}),"serial.write":e=>n({type:"serial.write.result",id:e.id}),"serial.close":e=>n({type:"serial.close.result",id:e.id})};function z(e={}){const t=e.now??(()=>Date.now()),i=[];function d(s){const o=s&&typeof s=="object"&&typeof s.type=="string"?s.type:void 0;if(i.push({envelope:s,verdict:K(s),timestamp:t()}),!o)return[];const a=Z[o];return a?a(s):[]}return{get records(){return i},handle:d,reset(){i.length=0}}}function V(e){return{manifestHtml:e.manifestHtml,sandbox:e.sandbox??{allowScripts:!0,allowSameOrigin:!1},installedGlobal:e.boot.installedGlobal,bootError:e.boot.bootError,emitted:e.boot.emitted,forbiddenGlobals:e.forbiddenGlobals??[],degraded:e.boot.degraded,lifecycle:e.lifecycle??null}}var B="__nappletConformance.error";function P(e){return new Promise(t=>setTimeout(t,e))}function Y(e){return e.replace(/&/g,"&").replace(/"/g,""").replace(/</g,"<").replace(/>/g,">")}function X(e){return`
|
|
2
|
-
(() => {
|
|
3
|
-
const domains = ${JSON.stringify(e)};
|
|
4
|
-
const napplet = {};
|
|
5
|
-
for (const domain of domains) napplet[domain] = {};
|
|
6
|
-
const pending = new Map();
|
|
7
|
-
let seq = 0;
|
|
8
|
-
const nextId = () => {
|
|
9
|
-
try {
|
|
10
|
-
if (window.crypto && typeof window.crypto.randomUUID === 'function') return window.crypto.randomUUID();
|
|
11
|
-
} catch {}
|
|
12
|
-
seq += 1;
|
|
13
|
-
return 'conformance-' + Date.now().toString(36) + '-' + seq.toString(36);
|
|
14
|
-
};
|
|
15
|
-
const request = (message, resultType, errorType) => new Promise((resolve, reject) => {
|
|
16
|
-
const id = message.id || nextId();
|
|
17
|
-
const timeout = window.setTimeout(() => {
|
|
18
|
-
pending.delete(id);
|
|
19
|
-
reject(new Error(message.type + ' timed out'));
|
|
20
|
-
}, 5000);
|
|
21
|
-
pending.set(id, { resolve, reject, resultType, errorType, timeout });
|
|
22
|
-
window.parent.postMessage({ ...message, id }, '*');
|
|
23
|
-
});
|
|
24
|
-
window.addEventListener('message', (event) => {
|
|
25
|
-
const msg = event.data;
|
|
26
|
-
if (!msg || typeof msg !== 'object' || typeof msg.id !== 'string') return;
|
|
27
|
-
const slot = pending.get(msg.id);
|
|
28
|
-
if (!slot) return;
|
|
29
|
-
if (msg.type !== slot.resultType && msg.type !== slot.errorType) return;
|
|
30
|
-
pending.delete(msg.id);
|
|
31
|
-
window.clearTimeout(slot.timeout);
|
|
32
|
-
if (msg.type === slot.errorType) {
|
|
33
|
-
slot.reject(new Error(msg.message ? msg.error + ': ' + msg.message : String(msg.error || 'request failed')));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
slot.resolve(msg);
|
|
37
|
-
});
|
|
38
|
-
if (napplet.resource) {
|
|
39
|
-
napplet.resource.info = () =>
|
|
40
|
-
request({ type: 'resource.info' }, 'resource.info.result', 'resource.info.error')
|
|
41
|
-
.then((msg) => msg.info);
|
|
42
|
-
napplet.resource.bytes = (url) =>
|
|
43
|
-
request({ type: 'resource.bytes', url: String(url) }, 'resource.bytes.result', 'resource.bytes.error')
|
|
44
|
-
.then((msg) => msg.blob);
|
|
45
|
-
napplet.resource.bytesMany = (urls) =>
|
|
46
|
-
request({ type: 'resource.bytesMany', urls: Array.from(urls || []) }, 'resource.bytesMany.result', 'resource.bytesMany.error')
|
|
47
|
-
.then((msg) => msg.items);
|
|
48
|
-
napplet.resource.bytesAsObjectURL = (url) => {
|
|
49
|
-
const handle = { url: '', revoke: () => {} };
|
|
50
|
-
let objectUrl = '';
|
|
51
|
-
let revoked = false;
|
|
52
|
-
const ready = napplet.resource.bytes(url).then((blob) => {
|
|
53
|
-
if (revoked) return;
|
|
54
|
-
objectUrl = URL.createObjectURL(blob);
|
|
55
|
-
handle.url = objectUrl;
|
|
56
|
-
return objectUrl;
|
|
57
|
-
});
|
|
58
|
-
handle.revoke = () => {
|
|
59
|
-
if (revoked) return;
|
|
60
|
-
revoked = true;
|
|
61
|
-
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
62
|
-
};
|
|
63
|
-
Object.defineProperty(handle, 'ready', { value: ready, enumerable: false });
|
|
64
|
-
return handle;
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
window.napplet = napplet;
|
|
68
|
-
const report = (reason) => {
|
|
69
|
-
try {
|
|
70
|
-
window.parent.postMessage({ type: '${B}', reason: String(reason && reason.message || reason) }, '*');
|
|
71
|
-
} catch {}
|
|
72
|
-
};
|
|
73
|
-
window.addEventListener('error', (event) => report(event.error || event.message));
|
|
74
|
-
window.addEventListener('unhandledrejection', (event) => report(event.reason));
|
|
75
|
-
})();
|
|
76
|
-
`}function Q(e,t,i){const d=`<base href="${Y(t)}">`,s=`<script>${X(i)}<\/script>`,o=/<head(?:\s[^>]*)?>/i.exec(e);if(o){const a=o.index+o[0].length;return`${e.slice(0,a)}${d}${s}${e.slice(a)}`}return`${d}${s}${e}`}function ee(e){if(!e.startsWith("data:"))return null;const t=e.indexOf(",");if(t<0)return"";const i=e.slice(0,t),d=e.slice(t+1);return i.endsWith(";base64")?atob(d):decodeURIComponent(d)}async function te(e){if(e==="about:blank")return"<!doctype html><html><head></head><body></body></html>";const t=ee(e);if(t!==null)return t;const i=await fetch(e);if(!i.ok)throw new Error(`Failed to fetch napplet HTML: ${i.status} ${i.statusText}`);return i.text()}async function L(e,t,i,d){const s=z(),o=i.createElement("iframe");o.setAttribute("sandbox",t.allowSameOrigin?"allow-scripts allow-same-origin":"allow-scripts"),o.setAttribute("aria-hidden","true"),o.style.position="absolute",o.style.width="1px",o.style.height="1px",o.style.left="-9999px";let a=null;const c=f=>{if(f.source!==o.contentWindow)return;const h=f.data;if(h&&typeof h=="object"&&h.type===B){a=`napplet boot error: ${String(h.reason??"unknown error")}`;return}const D=s.handle(f.data),H=f.source;for(const _ of D)H?.postMessage(_,"*")};let l=!1;const y=new Promise(f=>{o.addEventListener("load",()=>{l=!0,f()},{once:!0})});d.addEventListener("message",c),(i.body??i.documentElement).appendChild(o);try{const f=await te(t.url);o.srcdoc=Q(f,t.url,e)}catch(f){a=f instanceof Error?f.message:String(f)}if(!a){const f=Symbol("timeout");await Promise.race([y.then(()=>"loaded"),P(t.readyTimeoutMs).then(()=>f)])===f||!l?a=`napplet did not boot (iframe did not load within ${t.readyTimeoutMs}ms; a conformant napplet must run under sandbox="allow-scripts" without allow-same-origin)`:await P(t.settleMs)}d.removeEventListener("message",c);try{o.remove()}catch{}return{installedGlobal:a===null,bootError:a,emitted:[...s.records]}}async function re(e){const t=e.document??document,i=e.window??window,d={url:e.url,readyTimeoutMs:e.readyTimeoutMs??5e3,settleMs:e.settleMs,allowSameOrigin:e.allowSameOrigin??!1},s=await L(R,d,t,i);let o=null;{const a=await L([],d,t,i);o={bootError:a.bootError,emitted:a.emitted}}return{installedGlobal:s.installedGlobal,bootError:s.bootError,emitted:s.emitted,degraded:o}}var ie=e=>({status:"pass",detail:e}),se=(e,t)=>({status:"fail",detail:e,diagnostics:t}),ne=e=>({status:"skip",detail:e}),u={pass:ie,fail:se,skip:ne};function oe(e,t){return $(e).errors.filter(i=>t.includes(i.code))}function T(e,t){return new RegExp(`name\\s*=\\s*["']${t}["']`,"i").test(e)}function j(e,t,i,d){return{id:e,area:"manifest",severity:"error",title:t,run:s=>{if(d&&!T(s.manifestHtml,d))return u.skip(`No ${d} declared`);const o=oe(s.manifestHtml,i);return o.length?u.fail(o[0].message,o):u.pass()}}}var de=[j("manifest/napplet-type","Declares a valid napplet-type",["missing-napplet-type","invalid-napplet-type"]),j("manifest/declared-naps","napplet-requires lists only real NAP domains",["unknown-required-nap"],"napplet-requires"),j("manifest/config-schema","Config schema is a draft-07 core subset",["invalid-config-schema"],"napplet-config-schema"),{id:"boot/sandbox-allow-scripts",area:"boot",severity:"error",title:"Runs under allow-scripts without allow-same-origin",run:e=>e.sandbox.allowScripts?e.sandbox.allowSameOrigin?u.fail("Napplet relied on allow-same-origin, which a shell must not grant"):u.pass():u.fail("Napplet was not loaded with allow-scripts")},{id:"boot/installs-global",area:"boot",severity:"error",title:"Runs after runtime-injected window.napplet is installed",run:e=>e.installedGlobal?u.pass():u.fail("runtime did not inject window.napplet before load")},{id:"boot/no-boot-error",area:"boot",severity:"error",title:"Boots without an uncaught error",run:e=>e.bootError?u.fail(e.bootError):u.pass()},{id:"boot/no-forbidden-globals",area:"boot",severity:"error",title:"Does not access forbidden browser authority surfaces",run:e=>e.forbiddenGlobals.length?u.fail(`Accessed forbidden surface(s): ${e.forbiddenGlobals.join(", ")}`,e.forbiddenGlobals):u.pass()},{id:"wire/envelope-well-formed",area:"wire",severity:"error",title:"Every emitted envelope is well-formed",run:e=>{if(e.emitted.length===0)return u.skip("Napplet emitted no envelopes");const t=e.emitted.filter(d=>!d.verdict.ok);if(t.length===0)return u.pass(`${e.emitted.length} envelope(s) valid`);const i=t.map(d=>({envelope:d.envelope,errors:d.verdict.errors}));return u.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 u.skip("Napplet emitted no envelopes");if(!T(e.manifestHtml,"napplet-requires"))return u.skip("No napplet-requires to compare against");const t=new Set($(e.manifestHtml).requires.map(s=>s.startsWith("nap:")?s.slice(4):s)),d=[...new Set(e.emitted.map(s=>s.verdict.domain).filter(s=>typeof s=="string"))].filter(s=>!t.has(s));return d.length?u.fail(`Emitted undeclared NAP domain(s): ${d.join(", ")}`,d):u.pass()}},{id:"degrade/domain-absence",area:"degradation",severity:"error",title:"Does not crash when no NAP domains are injected",run:e=>e.degraded?e.degraded.bootError?u.fail(`Crashed with no injected domains: ${e.degraded.bootError}`):u.pass():u.skip("Host did not run the degraded (no-capabilities) pass")},{id:"lifecycle/clean-teardown",area:"lifecycle",severity:"warning",title:"Tears down listeners on unload",run:e=>e.lifecycle?e.lifecycle.listenerLeak?u.fail("Message listeners remained after unload"):u.pass():u.skip("Lifecycle not measured")}];function le(e,t={}){const i=t.now??(()=>Date.now()),d=t.checks??de,s=i(),o=d.map(l=>{const y=l.run(e);return{id:l.id,area:l.area,severity:l.severity,title:l.title,status:y.status,detail:y.detail,diagnostics:y.diagnostics}}),a={total:o.length,passed:o.filter(l=>l.status==="pass").length,failed:o.filter(l=>l.status==="fail").length,skipped:o.filter(l=>l.status==="skip").length,errors:o.filter(l=>l.status==="fail"&&l.severity==="error").length,warnings:o.filter(l=>l.status==="fail"&&l.severity==="warning").length};return{napplet:$(e.manifestHtml).nappletType,startedAt:s,finishedAt:i(),checks:o,summary:a,ok:a.errors===0}}const ae=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,ue="/__conformance__/events",C=new URLSearchParams(location.search),U=C.get("live")==="1";function b(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}const g=document.getElementById("app");function ce(){return g.innerHTML=`
|
|
77
|
-
<header class="masthead">
|
|
78
|
-
<h1>napplet conformance ${U?'<span class="live-badge" id="live-badge">● live</span>':""}</h1>
|
|
79
|
-
<p>Load a napplet by URL and run NAP protocol conformance live. Same engine as <code>napplet-conformance</code> CI.</p>
|
|
80
|
-
</header>
|
|
81
|
-
<form id="run-form" class="runbar">
|
|
82
|
-
<input id="url" type="url" placeholder="https://localhost:5173/ (a napplet served with permissive CORS)" autocomplete="off" />
|
|
83
|
-
<button id="run" type="submit">Run conformance</button>
|
|
84
|
-
<button id="rerun" type="button" class="secondary" hidden>Re-run</button>
|
|
85
|
-
</form>
|
|
86
|
-
<p id="status" class="status" role="status"></p>
|
|
87
|
-
<section id="output" class="output"></section>
|
|
88
|
-
`,{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 m=ce();let v="",A=0;function k(e,t="info"){m.status.textContent=e,m.status.dataset.kind=t}function I(e){return String(e).padStart(2,"0")}function pe(e){const t=new Date(e);return`${I(t.getHours())}:${I(t.getMinutes())}:${I(t.getSeconds())}`}async function fe(e){try{const i=await(await fetch(e,{mode:"cors"})).text();return{html:i,forbidden:ae.test(i)?["window.nostr"]:[],fetched:!0}}catch{return{html:"",forbidden:[],fetched:!1}}}function me(e){const t=e.ok?"conformant":"non-conformant",d=[...new Set(e.checks.map(a=>a.area))].map(a=>{const c=e.checks.filter(l=>l.area===a).map(l=>{const y=l.severity==="warning"&&l.status==="fail"?' <span class="tag warn">warning</span>':"",f=l.detail?`<span class="detail">${b(l.detail)}</span>`:"";return`<li class="check ${l.status}"><span class="badge ${l.status}">${l.status}</span><span class="id">${b(l.id)}</span>${y}${f}</li>`}).join("");return`<div class="area"><h3>${b(a)}</h3><ul>${c}</ul></div>`}).join(""),s=e.summary,o=A>1?`<span class="run-stamp">run #${A} · ${pe(e.finishedAt)}</span>`:"";return`
|
|
89
|
-
<div class="verdict ${t}">
|
|
90
|
-
<span class="result">${e.ok?"CONFORMANT":"NON-CONFORMANT"}</span>
|
|
91
|
-
<span class="counts">${s.passed} passed · ${s.failed} failed · ${s.skipped} skipped${s.warnings?` · ${s.warnings} warning(s)`:""}</span>
|
|
92
|
-
${o}
|
|
93
|
-
</div>
|
|
94
|
-
<div class="checks">${d}</div>
|
|
95
|
-
`}function ye(e){return e.length===0?'<p class="empty">No envelopes were emitted.</p>':`<ul class="envlog">${e.map(i=>{const d=i.verdict.ok?"ok":"bad",s=i.verdict.ok?"":`<div class="errs">${i.verdict.errors.map(o=>b(`${o.code}: ${o.message}`)).join("<br>")}</div>`;return`<li class="env ${d}"><code>${b(JSON.stringify(i.envelope))}</code>${s}</li>`}).join("")}</ul>`}function be(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=$(e),d=(s,o)=>`<tr><th>${b(s)}</th><td>${o?b(String(o)):"<em>—</em>"}</td></tr>`;return`
|
|
96
|
-
<table class="manifest">
|
|
97
|
-
${d("napplet-type",i.nappletType)}
|
|
98
|
-
${d("requires",i.requires.join(", "))}
|
|
99
|
-
</table>
|
|
100
|
-
${i.errors.length?`<div class="errs">${i.errors.map(s=>b(`${s.code}: ${s.message}`)).join("<br>")}</div>`:""}
|
|
101
|
-
`}function ge(e,t,i,d){m.output.innerHTML=`
|
|
102
|
-
${me(e)}
|
|
103
|
-
<div class="panels">
|
|
104
|
-
<section class="panel"><h2>Emitted envelopes (${t.length})</h2>${ye(t)}</section>
|
|
105
|
-
<section class="panel"><h2>Manifest</h2>${be(i,d)}</section>
|
|
106
|
-
</div>
|
|
107
|
-
`}async function E(e){v=e,m.runButton.disabled=!0,m.rerunButton.disabled=!0;try{k(`Fetching manifest for ${e} …`);const{html:t,forbidden:i,fetched:d}=await fe(e);k("Booting napplet in a sandboxed iframe …");const s=await re({url:e,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),o=V({manifestHtml:t,boot:s,forbiddenGlobals:i}),a=le(o);A+=1,k(a.ok?"Done — conformant.":"Done — non-conformant.",a.ok?"info":"error"),ge(a,s.emitted,t,d),m.rerunButton.hidden=!1}catch(t){k(`Error: ${t instanceof Error?t.message:String(t)}`,"error")}finally{m.runButton.disabled=!1,m.rerunButton.disabled=!1}}function he(){if(!U||typeof EventSource>"u")return;let e=!1;const t=new EventSource(ue);t.addEventListener("rerun",()=>{!v||e||(e=!0,setTimeout(()=>{e=!1,E(v)},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 i=new URL(location.href);i.searchParams.set("url",t),history.replaceState(null,"",i),E(t)}});m.rerunButton.addEventListener("click",()=>{v&&E(v)});he();const q=C.get("url");q&&(m.urlInput.value=q,E(q));
|