@napplet/conformance-cli 0.2.14 → 0.2.16

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/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # @napplet/conformance-cli
2
2
 
3
- The headless `napplet-conformance` runner. It drives the
4
- [`@napplet/conformance`](../conformance) engine against a napplet in real headless
5
- Chromium (via Playwright), so a napplet can prove it conforms to the NAP protocol
6
- **before** publishing — locally and in CI.
3
+ The headless `napplet-conformance` runner. It drives the [`@napplet/conformance`](../conformance) engine against a napplet in real headless Chromium (via Playwright), so a napplet can prove it conforms to the NAP protocol **before** publishing — locally and in CI.
4
+
5
+ The runner validates NIP-5D/NAP envelope and manifest carriers. It does not define payload schemas or matching behavior for an archetype convention. Intent acceptance and target delivery are distinct: the reference shell uses an authenticated endpoint fixture for sender provenance, returns acceptance first, and queues a separate no-ID target delivery. Payloads remain untrusted.
6
+
7
+ Archetype metadata is one queryless convention per tag with optional trailing same-tag `kind:<number>` discovery fields; kinds are not inferred from payloads. This non-normative description follows the adopted [NAP-INC #89 `4593ce9`](https://github.com/napplet/naps/blob/4593ce9e301ce098fd3dad64206fcd6f144fa7af/naps/NAP-INC.md), [URI terminology #90 `896c32c`](https://github.com/napplet/naps/commit/896c32c92deee68dc4d10fc1132b62df20cccb6f), and [NAP-INTENT #91 `a718915`](https://github.com/napplet/naps/blob/a718915ddefa2f03a0126579601f59d8bd86f7c4/naps/NAP-INTENT.md).
7
8
 
8
9
  ```bash
9
10
  # Build your napplet first, then:
@@ -35,25 +36,13 @@ pnpm test:conformance # npm / yarn / bun all work — the bin is PM-agnostic
35
36
  napplet-conformance --ui . --exec "vite build --watch"
36
37
  ```
37
38
 
38
- `--ui` serves the standalone conformance web runtime (bundled with this package) plus
39
- the napplet, opens your browser, and **re-runs conformance live every time the napplet
40
- changes**. The optional `--exec` runs your build in watch mode so source edits rebuild
41
- the served `./dist`; the CLI's file watcher then triggers a fresh run — edit, save, see
42
- the verdict update. Useful flags: `--port <n>`, `--no-open`. (Headless mode is
43
- unchanged — `--ui` is purely additive.)
39
+ `--ui` serves the standalone conformance web runtime (bundled with this package) plus the napplet, opens your browser, and **re-runs conformance live every time the napplet changes**. The optional `--exec` runs your build in watch mode so source edits rebuild the served `./dist`; the CLI's file watcher then triggers a fresh run — edit, save, see the verdict update. Useful flags: `--port <n>`, `--no-open`. (Headless mode is unchanged — `--ui` is purely additive.)
44
40
 
45
41
  ## How it works
46
42
 
47
- 1. Serves your built napplet on loopback alongside a host harness page and the
48
- bundled engine. Every response sends `Access-Control-Allow-Origin: *` so the
49
- sandboxed napplet's module scripts load across its opaque origin.
50
- 2. Launches headless Chromium, loads the napplet into a `sandbox="allow-scripts"`
51
- iframe (no `allow-same-origin`), attaches a reference shell, and records every
52
- envelope the napplet emits — plus a second no-capability pass to prove graceful
53
- degradation.
54
- 3. Assembles the conformance context (manifest + static `window.nostr` scan), runs
55
- the check catalog, prints the report, and exits non-zero on any error-severity
56
- failure.
43
+ 1. Serves your built napplet on loopback alongside a host harness page and the bundled engine. Every response sends `Access-Control-Allow-Origin: *` so the sandboxed napplet's module scripts load across its opaque origin.
44
+ 2. Launches headless Chromium, loads the napplet into a `sandbox="allow-scripts"` iframe (no `allow-same-origin`), attaches a reference shell, and records every envelope the napplet emits — plus a second no-capability pass to prove graceful degradation.
45
+ 3. Assembles the conformance context (manifest + static `window.nostr` scan), runs the check catalog, prints the report, and exits non-zero on any error-severity failure.
57
46
 
58
47
  ## Options
59
48
 
@@ -74,7 +74,7 @@ var NappletConformanceHostBundle = (() => {
74
74
  "storage.remove.result": { dir: "in" },
75
75
  "storage.keys.result": { dir: "in" },
76
76
  // ── inc (inter-napplet communication) ─────────────────────────────────────
77
- "inc.emit": { dir: "out", fields: { topic: "string" } },
77
+ "inc.emit": { dir: "out", fields: { topic: "string" }, forbiddenFields: ["sender"] },
78
78
  "inc.subscribe": { dir: "out", fields: { ...ID, topic: "string" } },
79
79
  "inc.unsubscribe": { dir: "out", fields: { topic: "string" } },
80
80
  "inc.channel.open": { dir: "out", fields: { ...ID, target: "string" } },
@@ -83,7 +83,7 @@ var NappletConformanceHostBundle = (() => {
83
83
  "inc.channel.list": { dir: "out", fields: { ...ID } },
84
84
  "inc.channel.close": { dir: "out", fields: { channelId: "string" } },
85
85
  "inc.subscribe.result": { dir: "in" },
86
- "inc.event": { dir: "in" },
86
+ "inc.event": { dir: "in", fields: { topic: "string", sender: "string" } },
87
87
  "inc.channel.open.result": { dir: "in" },
88
88
  "inc.channel.event": { dir: "in" },
89
89
  "inc.channel.list.result": { dir: "in" },
@@ -188,6 +188,7 @@ var NappletConformanceHostBundle = (() => {
188
188
  "intent.available.result": { dir: "in" },
189
189
  "intent.handlers.result": { dir: "in" },
190
190
  "intent.changed": { dir: "in" },
191
+ "intent.deliver": { dir: "in", fields: { delivery: "object" } },
191
192
  // ── ble ──────────────────────────────────────────────────────────────────
192
193
  "ble.open": { dir: "out", fields: { ...ID, request: "object" } },
193
194
  "ble.services": { dir: "out", fields: { ...ID, sessionId: "string" } },
@@ -278,6 +279,43 @@ var NappletConformanceHostBundle = (() => {
278
279
  if (kind === "present") return value !== void 0 && value !== null;
279
280
  return kindOf(value) === kind;
280
281
  }
282
+ function validateIntentInvokeRequest(request, errors) {
283
+ if (typeof request !== "object" || request === null || Array.isArray(request)) return;
284
+ const normalized = request;
285
+ for (const field of ["archetype", "action", "convention"]) {
286
+ const value = normalized[field];
287
+ if (value === void 0) {
288
+ errors.push({
289
+ code: "missing-field",
290
+ message: `Intent request requires a string "${field}" field`,
291
+ field: `request.${field}`
292
+ });
293
+ } else if (typeof value !== "string") {
294
+ errors.push({
295
+ code: "wrong-type",
296
+ message: `Intent request field "${field}" must be a string`,
297
+ field: `request.${field}`
298
+ });
299
+ }
300
+ }
301
+ if ("sender" in normalized) {
302
+ errors.push({
303
+ code: "forbidden-field",
304
+ message: "Intent request sender is runtime-derived and cannot be emitted by a napplet",
305
+ field: "request.sender"
306
+ });
307
+ }
308
+ const { archetype, action, convention } = normalized;
309
+ if (typeof archetype !== "string" || typeof action !== "string" || typeof convention !== "string") return;
310
+ const parsed = /^napplet:([^/?#\s]+)\/([^/?#\s]+)$/.exec(convention);
311
+ if (!parsed || parsed[1] !== archetype || parsed[2] !== action) {
312
+ errors.push({
313
+ code: "invalid-intent-request",
314
+ message: "Intent convention must be queryless and match request archetype and action",
315
+ field: "request.convention"
316
+ });
317
+ }
318
+ }
281
319
  function validateEnvelope(message) {
282
320
  const errors = [];
283
321
  if (typeof message !== "object" || message === null || Array.isArray(message)) {
@@ -301,15 +339,6 @@ var NappletConformanceHostBundle = (() => {
301
339
  if (!spec) {
302
340
  return { ok: false, type, domain, errors: [{ code: "unknown-type", message: `"${type}" is not a known ${domain} message type` }] };
303
341
  }
304
- if (spec.dir === "in") {
305
- return {
306
- ok: false,
307
- type,
308
- domain,
309
- direction: "in",
310
- errors: [{ code: "inbound-type-emitted", message: `"${type}" is a shell\u2192napplet message; a napplet must not emit it` }]
311
- };
312
- }
313
342
  for (const [field, kind] of Object.entries(spec.fields ?? {})) {
314
343
  if (!(field in record) || record[field] === void 0) {
315
344
  errors.push({ code: "missing-field", message: `Required field "${field}" is missing`, field });
@@ -323,85 +352,129 @@ var NappletConformanceHostBundle = (() => {
323
352
  });
324
353
  }
325
354
  }
355
+ if (spec.dir === "in") {
356
+ return {
357
+ ok: false,
358
+ type,
359
+ domain,
360
+ direction: "in",
361
+ errors: [
362
+ { code: "inbound-type-emitted", message: `"${type}" is a shell\u2192napplet message; a napplet must not emit it` },
363
+ ...errors
364
+ ]
365
+ };
366
+ }
367
+ for (const field of spec.forbiddenFields ?? []) {
368
+ if (field in record) {
369
+ errors.push({
370
+ code: "forbidden-field",
371
+ message: `Field "${field}" must be runtime-derived and cannot be emitted by a napplet`,
372
+ field
373
+ });
374
+ }
375
+ }
376
+ if (type === "intent.invoke") {
377
+ validateIntentInvokeRequest(record.request, errors);
378
+ }
326
379
  return { ok: errors.length === 0, type, domain, direction: "out", errors };
327
380
  }
328
- var NAPPLET_TYPE_RE = /^[a-z0-9][a-z0-9._:-]*$/;
329
- function decodeHtmlEntities(value) {
330
- return value.replace(/&(#x?[0-9a-fA-F]+|[a-zA-Z]+);/g, (match, entity) => {
331
- if (entity[0] === "#") {
332
- const code = entity[1] === "x" || entity[1] === "X" ? parseInt(entity.slice(2), 16) : parseInt(entity.slice(1), 10);
333
- return Number.isFinite(code) ? String.fromCodePoint(code) : match;
334
- }
335
- const named = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'" };
336
- return named[entity] ?? match;
337
- });
381
+ var NAPPLET_KIND_SNAPSHOT = 5129;
382
+ var NAPPLET_KIND_ROOT = 15129;
383
+ var NAPPLET_KIND_NAMED = 35129;
384
+ var NAPPLET_MANIFEST_KINDS = [
385
+ NAPPLET_KIND_SNAPSHOT,
386
+ NAPPLET_KIND_ROOT,
387
+ NAPPLET_KIND_NAMED
388
+ ];
389
+ function firstTag(event, name) {
390
+ return event.tags.find((tag) => tag[0] === name);
391
+ }
392
+ function isNappletKind(kind) {
393
+ return NAPPLET_MANIFEST_KINDS.includes(kind);
394
+ }
395
+ function isSha256Hex(value) {
396
+ return typeof value === "string" && /^[0-9a-f]{64}$/i.test(value);
338
397
  }
339
- function readMeta(html, name) {
340
- const tagRe = /<meta\b[^>]*>/gi;
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;
398
+ function manifestDTag(event) {
399
+ const d = firstTag(event, "d")?.[1]?.trim();
400
+ return d || void 0;
351
401
  }
352
- function usesPatternKeyword(node) {
353
- if (Array.isArray(node)) return node.some(usesPatternKeyword);
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;
402
+ function manifestRequires(event) {
403
+ return event.tags.filter((tag) => tag[0] === "requires").map((tag) => tag[1]?.trim() ?? "").filter(Boolean);
360
404
  }
361
- function validateManifest(html, _options = {}) {
405
+ function validateManifestEvent(event) {
362
406
  const errors = [];
363
407
  const warnings = [];
364
- const nappletType = readMeta(html, "napplet-type");
365
- if (!nappletType) {
366
- errors.push({ code: "missing-napplet-type", message: 'No <meta name="napplet-type"> declared' });
367
- } else if (!NAPPLET_TYPE_RE.test(nappletType)) {
368
- errors.push({ code: "invalid-napplet-type", message: `napplet-type "${nappletType}" must match ${NAPPLET_TYPE_RE}` });
369
- }
370
- const requiresRaw = readMeta(html, "napplet-requires");
371
- const requires = requiresRaw ? requiresRaw.split(",").map((s) => s.trim()).filter(Boolean) : [];
372
- for (const req of requires) {
373
- const domain = req.startsWith("nap:") ? req.slice("nap:".length) : req;
374
- if (!NAP_DOMAINS.includes(domain)) {
375
- errors.push({ code: "unknown-required-nap", message: `napplet-requires lists "${req}" which is not a known NAP domain` });
376
- }
408
+ if (!event) {
409
+ return {
410
+ ok: false,
411
+ requires: [],
412
+ errors: [{ code: "missing-manifest-event", message: "No NIP-5D manifest event was resolved" }],
413
+ warnings
414
+ };
415
+ }
416
+ const dTag = manifestDTag(event);
417
+ const requires = manifestRequires(event);
418
+ if (!isNappletKind(event.kind)) {
419
+ errors.push({
420
+ code: "invalid-napplet-kind",
421
+ message: `Manifest event kind ${event.kind} is not a NIP-5D napplet kind`
422
+ });
423
+ }
424
+ if (event.kind === NAPPLET_KIND_NAMED && !dTag) {
425
+ errors.push({
426
+ code: "missing-d-tag",
427
+ message: "Named napplet manifest kind 35129 must include a non-empty d tag"
428
+ });
429
+ } else if ((event.kind === NAPPLET_KIND_ROOT || event.kind === NAPPLET_KIND_SNAPSHOT) && dTag) {
430
+ errors.push({
431
+ code: "unexpected-d-tag",
432
+ message: `Napplet manifest kind ${event.kind} must not include a d tag`
433
+ });
434
+ }
435
+ const indexPath = event.tags.find((tag) => tag[0] === "path" && tag[1] === "/index.html");
436
+ if (!indexPath) {
437
+ errors.push({
438
+ code: "missing-index-html",
439
+ message: "Napplet manifest must include a path tag for /index.html"
440
+ });
441
+ } else if (!isSha256Hex(indexPath[2])) {
442
+ errors.push({
443
+ code: "invalid-index-html-hash",
444
+ message: "The /index.html path tag must carry a 64-character sha256 hash"
445
+ });
377
446
  }
378
- const configSchemaRaw = readMeta(html, "napplet-config-schema");
379
- if (configSchemaRaw) {
380
- let parsed;
381
- try {
382
- parsed = JSON.parse(configSchemaRaw);
383
- } catch {
384
- parsed = void 0;
385
- errors.push({ code: "invalid-config-schema", message: "napplet-config-schema is not valid JSON" });
447
+ for (const req of requires) {
448
+ if (req.startsWith("nap:") || req.startsWith("NAP-")) {
449
+ errors.push({
450
+ code: "invalid-required-nap",
451
+ message: `requires tag "${req}" must be a bare NAP domain such as "relay"`
452
+ });
453
+ continue;
386
454
  }
387
- if (parsed !== void 0) {
388
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
389
- errors.push({ code: "invalid-config-schema", message: "napplet-config-schema must be a JSON Schema object" });
390
- } else if (usesPatternKeyword(parsed)) {
391
- errors.push({ code: "invalid-config-schema", message: "napplet-config-schema uses the `pattern` keyword, which the draft-07 core subset excludes (CVE-2025-69873)" });
392
- }
455
+ if (!NAP_DOMAINS.includes(req)) {
456
+ errors.push({
457
+ code: "unknown-required-nap",
458
+ message: `requires tag "${req}" is not a known NAP domain`
459
+ });
393
460
  }
394
461
  }
395
462
  return {
396
463
  ok: errors.length === 0,
397
- nappletType,
464
+ kind: event.kind,
465
+ dTag,
398
466
  requires,
399
467
  errors,
400
468
  warnings
401
469
  };
402
470
  }
403
471
  var REFERENCE_PUBKEY = "f".repeat(64);
472
+ var REFERENCE_ENDPOINT = { dTag: "reference-source" };
404
473
  var REFERENCE_BLOB_URL = "https://reference.invalid/blob";
474
+ var REFERENCE_HANDLER = "reference-handler";
475
+ var REFERENCE_SUBSCRIBER = "reference-subscriber";
476
+ var REFERENCE_CONVENTION = "napplet:note/open";
477
+ var REFERENCE_CONTRACT = { convention: REFERENCE_CONVENTION, eventKinds: [1, 30023] };
405
478
  var ok = (v) => [v];
406
479
  var none = () => [];
407
480
  function dataUrlToBlob(url) {
@@ -537,27 +610,6 @@ var NappletConformanceHostBundle = (() => {
537
610
  }),
538
611
  "upload.upload": (e) => ok({ type: "upload.upload.result", id: e.id, result: { url: REFERENCE_BLOB_URL } }),
539
612
  "upload.status": (e) => ok({ type: "upload.status.result", id: e.id, status: {} }),
540
- // intent
541
- "intent.invoke": (e) => ok({ type: "intent.invoke.result", id: e.id, result: { ok: true, archetype: "reference", action: "open", handled: true } }),
542
- "intent.available": (e) => ok({
543
- type: "intent.available.result",
544
- id: e.id,
545
- availability: {
546
- archetype: e.archetype,
547
- available: true,
548
- candidates: [
549
- {
550
- dTag: "reference-handler",
551
- actions: ["open"],
552
- protocols: ["NAP-4"],
553
- contracts: [{ action: "open", protocol: "NAP-4", eventKinds: [1] }],
554
- isDefault: true
555
- }
556
- ],
557
- hasDefault: true
558
- }
559
- }),
560
- "intent.handlers": (e) => ok({ type: "intent.handlers.result", id: e.id, handlers: [] }),
561
613
  // ble
562
614
  "ble.open": (e) => ok({
563
615
  type: "ble.open.result",
@@ -612,20 +664,112 @@ var NappletConformanceHostBundle = (() => {
612
664
  function createReferenceShell(options = {}) {
613
665
  const now = options.now ?? (() => Date.now());
614
666
  const records = [];
615
- function handle(envelope) {
667
+ const targetQueues = /* @__PURE__ */ new Map();
668
+ function queueDelivery(target, delivery) {
669
+ const queue = targetQueues.get(target);
670
+ if (queue) {
671
+ queue.push(delivery);
672
+ return;
673
+ }
674
+ targetQueues.set(target, [delivery]);
675
+ }
676
+ function takeDeliveries(target) {
677
+ const queue = targetQueues.get(target) ?? [];
678
+ targetQueues.delete(target);
679
+ return queue;
680
+ }
681
+ function unavailableIntent(id, error) {
682
+ return ok({ type: "intent.invoke.result", id, result: { ok: false, error } });
683
+ }
684
+ function handleIntentInvoke(endpoint, env) {
685
+ const request = env.request;
686
+ if (typeof request !== "object" || request === null || Array.isArray(request)) {
687
+ return unavailableIntent(env.id, "invalid intent request");
688
+ }
689
+ const normalized = request;
690
+ const { archetype, action, convention } = normalized;
691
+ if (typeof archetype !== "string" || typeof action !== "string" || typeof convention !== "string") {
692
+ return unavailableIntent(env.id, "intent request must carry normalized identity");
693
+ }
694
+ const parsed = /^napplet:([^/?#\s]+)\/([^/?#\s]+)$/.exec(convention);
695
+ if (!parsed || parsed[1] !== archetype || parsed[2] !== action) {
696
+ return unavailableIntent(env.id, "intent request conflicts with its convention");
697
+ }
698
+ if (convention !== REFERENCE_CONVENTION) {
699
+ return unavailableIntent(env.id, "no reference handler for convention");
700
+ }
701
+ const delivery = {
702
+ sender: endpoint.dTag,
703
+ archetype,
704
+ action,
705
+ convention
706
+ };
707
+ if ("payload" in normalized) delivery.payload = normalized.payload;
708
+ queueDelivery(REFERENCE_HANDLER, { type: "intent.deliver", delivery });
709
+ return ok({
710
+ type: "intent.invoke.result",
711
+ id: env.id,
712
+ result: { ok: true, archetype, action, convention, handler: REFERENCE_HANDLER }
713
+ });
714
+ }
715
+ function intentAvailability(archetype) {
716
+ if (archetype !== "note") {
717
+ return { archetype, available: false, candidates: [], hasDefault: false };
718
+ }
719
+ return {
720
+ archetype,
721
+ available: true,
722
+ candidates: [{
723
+ dTag: REFERENCE_HANDLER,
724
+ actions: ["open"],
725
+ conventions: [REFERENCE_CONVENTION],
726
+ contracts: [REFERENCE_CONTRACT],
727
+ isDefault: true
728
+ }],
729
+ hasDefault: true
730
+ };
731
+ }
732
+ function handleFrom(endpoint, envelope) {
616
733
  const type = envelope && typeof envelope === "object" && typeof envelope.type === "string" ? envelope.type : void 0;
617
- records.push({ envelope, verdict: validateEnvelope(envelope), timestamp: now() });
618
- if (!type) return [];
734
+ const verdict = validateEnvelope(envelope);
735
+ records.push({ envelope, verdict, timestamp: now() });
736
+ if (!type || !verdict.ok) return [];
737
+ const env = envelope;
738
+ if (type === "intent.invoke") return handleIntentInvoke(endpoint, env);
739
+ if (type === "intent.available") {
740
+ return ok({ type: "intent.available.result", id: env.id, availability: intentAvailability(env.archetype) });
741
+ }
742
+ if (type === "intent.handlers") {
743
+ return ok({ type: "intent.handlers.result", id: env.id, handlers: [intentAvailability("note")] });
744
+ }
745
+ if (type === "inc.emit") {
746
+ if (env.topic === REFERENCE_CONVENTION) {
747
+ const event = {
748
+ type: "inc.event",
749
+ topic: env.topic,
750
+ sender: endpoint.dTag
751
+ };
752
+ if ("payload" in env) event.payload = env.payload;
753
+ queueDelivery(REFERENCE_SUBSCRIBER, event);
754
+ }
755
+ return [];
756
+ }
619
757
  const responder = RESPONDERS[type];
620
- return responder ? responder(envelope) : [];
758
+ return responder ? responder(env) : [];
759
+ }
760
+ function handle(envelope) {
761
+ return handleFrom(REFERENCE_ENDPOINT, envelope);
621
762
  }
622
763
  return {
623
764
  get records() {
624
765
  return records;
625
766
  },
626
767
  handle,
768
+ handleFrom,
769
+ takeDeliveries,
627
770
  reset() {
628
771
  records.length = 0;
772
+ targetQueues.clear();
629
773
  }
630
774
  };
631
775
  }
@@ -819,30 +963,37 @@ var NappletConformanceHostBundle = (() => {
819
963
  var fail = (detail, diagnostics) => ({ status: "fail", detail, diagnostics });
820
964
  var skip = (detail) => ({ status: "skip", detail });
821
965
  var result = { pass, fail, skip };
822
- function manifestErrors(html, codes) {
823
- return validateManifest(html).errors.filter((e) => codes.includes(e.code));
824
- }
825
- function hasMeta(html, name) {
826
- return new RegExp(`name\\s*=\\s*["']${name}["']`, "i").test(html);
966
+ function manifestErrors(ctx, codes) {
967
+ return validateManifestEvent(ctx.manifestEvent).errors.filter((e) => codes.includes(e.code));
827
968
  }
828
- function manifestCheck(id, title, codes, skipMeta) {
969
+ function manifestCheck(id, title, codes) {
829
970
  return {
830
971
  id,
831
972
  area: "manifest",
832
973
  severity: "error",
833
974
  title,
834
975
  run: (ctx) => {
835
- if (skipMeta && !hasMeta(ctx.manifestHtml, skipMeta)) return result.skip(`No ${skipMeta} declared`);
836
- const errs = manifestErrors(ctx.manifestHtml, codes);
976
+ if (!ctx.manifestEvent) return result.skip("No NIP-5D manifest event was resolved");
977
+ const errs = manifestErrors(ctx, codes);
837
978
  return errs.length ? result.fail(errs[0].message, errs) : result.pass();
838
979
  }
839
980
  };
840
981
  }
841
982
  var CHECKS = [
842
983
  // ── manifest ───────────────────────────────────────────────────────────────
843
- manifestCheck("manifest/napplet-type", "Declares a valid napplet-type", ["missing-napplet-type", "invalid-napplet-type"]),
844
- manifestCheck("manifest/declared-naps", "napplet-requires lists only real NAP domains", ["unknown-required-nap"], "napplet-requires"),
845
- manifestCheck("manifest/config-schema", "Config schema is a draft-07 core subset", ["invalid-config-schema"], "napplet-config-schema"),
984
+ manifestCheck("manifest/event-kind", "Resolved event is a NIP-5D napplet manifest", [
985
+ "invalid-napplet-kind",
986
+ "missing-d-tag",
987
+ "unexpected-d-tag"
988
+ ]),
989
+ manifestCheck("manifest/index-html", "Manifest declares a hashed /index.html", [
990
+ "missing-index-html",
991
+ "invalid-index-html-hash"
992
+ ]),
993
+ manifestCheck("manifest/requires", "requires tags are bare known NAP domains", [
994
+ "invalid-required-nap",
995
+ "unknown-required-nap"
996
+ ]),
846
997
  // ── boot ─────────────────────────────────────────────────────────────────────
847
998
  {
848
999
  id: "boot/sandbox-allow-scripts",
@@ -890,22 +1041,6 @@ var NappletConformanceHostBundle = (() => {
890
1041
  return result.fail(`${bad.length}/${ctx.emitted.length} emitted envelope(s) invalid`, diag);
891
1042
  }
892
1043
  },
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
1044
  // ── degradation ───────────────────────────────────────────────────────────────
910
1045
  {
911
1046
  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 ot=["relay","identity","storage","inc","theme","keys","media","notify","config","resource","cvm","outbox","upload","intent","ble","webrtc","link","count","lists","serial","common","dm"],f={id:"string"},Sn={"relay.subscribe":{dir:"out",fields:{...f,subId:"string",filters:"array"}},"relay.close":{dir:"out",fields:{...f,subId:"string"}},"relay.publish":{dir:"out",fields:{...f,event:"object"}},"relay.publishEncrypted":{dir:"out",fields:{...f,event:"object",recipient:"string"}},"relay.query":{dir:"out",fields:{...f,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:{...f}},"identity.getRelays":{dir:"out",fields:{...f}},"identity.getProfile":{dir:"out",fields:{...f}},"identity.getFollows":{dir:"out",fields:{...f}},"identity.getList":{dir:"out",fields:{...f,listType:"string"}},"identity.getZaps":{dir:"out",fields:{...f}},"identity.getMutes":{dir:"out",fields:{...f}},"identity.getBlocked":{dir:"out",fields:{...f}},"identity.getBadges":{dir:"out",fields:{...f}},"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:{...f,key:"string"}},"storage.set":{dir:"out",fields:{...f,key:"string",value:"string"}},"storage.remove":{dir:"out",fields:{...f,key:"string"}},"storage.keys":{dir:"out",fields:{...f}},"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"},forbiddenFields:["sender"]},"inc.subscribe":{dir:"out",fields:{...f,topic:"string"}},"inc.unsubscribe":{dir:"out",fields:{topic:"string"}},"inc.channel.open":{dir:"out",fields:{...f,target:"string"}},"inc.channel.emit":{dir:"out",fields:{channelId:"string"}},"inc.channel.broadcast":{dir:"out"},"inc.channel.list":{dir:"out",fields:{...f}},"inc.channel.close":{dir:"out",fields:{channelId:"string"}},"inc.subscribe.result":{dir:"in"},"inc.event":{dir:"in",fields:{topic:"string",sender:"string"}},"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:{...f}},"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:{...f,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:{...f,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:{...f,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:{...f}},"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:{...f,schema:"object"}},"config.get":{dir:"out",fields:{...f}},"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:{...f}},"resource.bytes":{dir:"out",fields:{...f,url:"string"}},"resource.bytesMany":{dir:"out",fields:{...f,urls:"array"}},"resource.cancel":{dir:"out",fields:{...f}},"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:{...f}},"cvm.request":{dir:"out",fields:{...f,server:"object",message:"object"}},"cvm.close":{dir:"out",fields:{...f,server:"object"}},"cvm.registry.list":{dir:"out",fields:{...f}},"cvm.registry.has":{dir:"out",fields:{...f,family:"string"}},"cvm.registry.describe":{dir:"out",fields:{...f,family:"string"}},"cvm.registry.call":{dir:"out",fields:{...f,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:{...f,eventId:"string"}},"outbox.query":{dir:"out",fields:{...f,filters:"present"}},"outbox.subscribe":{dir:"out",fields:{...f,subId:"string",filters:"present"}},"outbox.close":{dir:"out",fields:{...f,subId:"string"}},"outbox.publish":{dir:"out",fields:{...f,event:"object"}},"outbox.resolveRelays":{dir:"out",fields:{...f,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:{...f}},"upload.upload":{dir:"out",fields:{...f,request:"object"}},"upload.status":{dir:"out",fields:{...f,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:{...f,request:"object"}},"intent.available":{dir:"out",fields:{...f,archetype:"string"}},"intent.handlers":{dir:"out",fields:{...f}},"intent.invoke.result":{dir:"in"},"intent.available.result":{dir:"in"},"intent.handlers.result":{dir:"in"},"intent.changed":{dir:"in"},"intent.deliver":{dir:"in",fields:{delivery:"object"}},"ble.open":{dir:"out",fields:{...f,request:"object"}},"ble.services":{dir:"out",fields:{...f,sessionId:"string"}},"ble.read":{dir:"out",fields:{...f,sessionId:"string",target:"object"}},"ble.write":{dir:"out",fields:{...f,sessionId:"string",target:"object",data:"array"}},"ble.subscribe":{dir:"out",fields:{...f,sessionId:"string",target:"object"}},"ble.unsubscribe":{dir:"out",fields:{...f,sessionId:"string",target:"object"}},"ble.close":{dir:"out",fields:{...f,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:{...f,input:"object"}},"common.decodeNip19":{dir:"out",fields:{...f,value:"string"}},"common.getProfile":{dir:"out",fields:{...f,target:"string"}},"common.follows":{dir:"out",fields:{...f}},"common.follow":{dir:"out",fields:{...f,pubkeys:"array"}},"common.unfollow":{dir:"out",fields:{...f,pubkeys:"array"}},"common.react":{dir:"out",fields:{...f,targetEventId:"string",reaction:"string"}},"common.report":{dir:"out",fields:{...f,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:{...f,request:"object"}},"webrtc.send":{dir:"out",fields:{...f,sessionId:"string",payload:"present"}},"webrtc.close":{dir:"out",fields:{...f,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:{...f,url:"string"}},"link.open.result":{dir:"in"},"count.query":{dir:"out",fields:{...f,filters:"array"}},"count.query.result":{dir:"in"},"lists.supported":{dir:"out",fields:{...f}},"lists.add":{dir:"out",fields:{...f,list:"object",items:"array"}},"lists.remove":{dir:"out",fields:{...f,list:"object",items:"array"}},"lists.supported.result":{dir:"in"},"lists.add.result":{dir:"in"},"lists.remove.result":{dir:"in"},"serial.open":{dir:"out",fields:{...f,request:"object"}},"serial.write":{dir:"out",fields:{...f,sessionId:"string",data:"array"}},"serial.close":{dir:"out",fields:{...f,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:{...f}},"dm.conversations":{dir:"out",fields:{...f}},"dm.messages":{dir:"out",fields:{...f,conversationId:"string"}},"dm.send":{dir:"out",fields:{...f,recipients:"array",content:"string"}},"dm.subscribe":{dir:"out",fields:{...f}},"dm.unsubscribe":{dir:"out",fields:{...f,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 Ut(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 $n(e,t){return t==="present"?e!=null:Ut(e)===t}function In(e,t){if(typeof e!="object"||e===null||Array.isArray(e))return;const r=e;for(const c of["archetype","action","convention"]){const a=r[c];a===void 0?t.push({code:"missing-field",message:`Intent request requires a string "${c}" field`,field:`request.${c}`}):typeof a!="string"&&t.push({code:"wrong-type",message:`Intent request field "${c}" must be a string`,field:`request.${c}`})}"sender"in r&&t.push({code:"forbidden-field",message:"Intent request sender is runtime-derived and cannot be emitted by a napplet",field:"request.sender"});const{archetype:n,action:i,convention:s}=r;if(typeof n!="string"||typeof i!="string"||typeof s!="string")return;const o=/^napplet:([^/?#\s]+)\/([^/?#\s]+)$/.exec(s);(!o||o[1]!==n||o[2]!==i)&&t.push({code:"invalid-intent-request",message:"Intent convention must be queryless and match request archetype and action",field:"request.convention"})}function Tn(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(!ot.includes(s))return{ok:!1,type:n,domain:s,errors:[{code:"unknown-domain",message:`"${s}" is not a known NAP domain`}]};const c=Sn[n];if(!c)return{ok:!1,type:n,domain:s,errors:[{code:"unknown-type",message:`"${n}" is not a known ${s} message type`}]};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}$n(r[a],l)||t.push({code:"wrong-type",message:`Field "${a}" should be ${l} but is ${Ut(r[a])}`,field:a})}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`},...t]};for(const a of c.forbiddenFields??[])a in r&&t.push({code:"forbidden-field",message:`Field "${a}" must be runtime-derived and cannot be emitted by a napplet`,field:a});return n==="intent.invoke"&&In(r.request,t),{ok:t.length===0,type:n,domain:s,direction:"out",errors:t}}var Ht=5129,jt=15129,ge=35129,at=[Ht,jt,ge];function Dt(e,t){return e.tags.find(r=>r[0]===t)}function An(e){return at.includes(e)}function Bn(e){return typeof e=="string"&&/^[0-9a-f]{64}$/i.test(e)}function Mt(e){return Dt(e,"d")?.[1]?.trim()||void 0}function On(e){return e.tags.filter(t=>t[0]==="requires").map(t=>t[1]?.trim()??"").filter(Boolean)}function Zt(e){return Mt(e)??Dt(e,"title")?.[1]??e.id}function ct(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=Mt(e),i=On(e);An(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===jt||e.kind===Ht)&&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?Bn(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}ot.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,requires:i,errors:t,warnings:r}}var xe="f".repeat(64),Nn={dTag:"reference-source"},qn="https://reference.invalid/blob",je="reference-handler",Ln="reference-subscriber",$e="napplet:note/open",Pn={convention:$e,eventKinds:[1,30023]},m=e=>[e],L=()=>[];function yt(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 _n={"relay.subscribe":e=>m({type:"relay.eose",subId:e.subId}),"relay.close":L,"relay.publish":e=>m({type:"relay.publish.result",id:e.id,ok:!0,event:e.event}),"relay.publishEncrypted":e=>m({type:"relay.publishEncrypted.result",id:e.id,ok:!0,event:e.event}),"relay.query":e=>m({type:"relay.query.result",id:e.id,events:[]}),"identity.getPublicKey":e=>m({type:"identity.getPublicKey.result",id:e.id,pubkey:xe}),"identity.getRelays":e=>m({type:"identity.getRelays.result",id:e.id,relays:{}}),"identity.getProfile":e=>m({type:"identity.getProfile.result",id:e.id,profile:null}),"identity.getFollows":e=>m({type:"identity.getFollows.result",id:e.id,pubkeys:[]}),"identity.getList":e=>m({type:"identity.getList.result",id:e.id,entries:[]}),"identity.getZaps":e=>m({type:"identity.getZaps.result",id:e.id,zaps:[]}),"identity.getMutes":e=>m({type:"identity.getMutes.result",id:e.id,pubkeys:[]}),"identity.getBlocked":e=>m({type:"identity.getBlocked.result",id:e.id,pubkeys:[]}),"identity.getBadges":e=>m({type:"identity.getBadges.result",id:e.id,badges:[]}),"storage.get":e=>m({type:"storage.get.result",id:e.id,value:null}),"storage.set":e=>m({type:"storage.set.result",id:e.id}),"storage.remove":e=>m({type:"storage.remove.result",id:e.id}),"storage.keys":e=>m({type:"storage.keys.result",id:e.id,keys:[]}),"inc.emit":L,"inc.subscribe":e=>m({type:"inc.subscribe.result",id:e.id}),"inc.unsubscribe":L,"inc.channel.open":e=>m({type:"inc.channel.open.result",id:e.id,channelId:`chan-${String(e.id)}`,peer:"reference-peer"}),"inc.channel.emit":L,"inc.channel.broadcast":L,"inc.channel.list":e=>m({type:"inc.channel.list.result",id:e.id,channels:[]}),"inc.channel.close":L,"theme.get":e=>m({type:"theme.get.result",id:e.id,theme:{colors:{},mode:"dark"}}),"keys.forward":L,"keys.registerAction":e=>m({type:"keys.registerAction.result",id:e.id,actionId:`action-${String(e.id)}`}),"keys.unregisterAction":L,"media.session.create":e=>m({type:"media.session.create.result",id:e.id,sessionId:`session-${String(e.id)}`,owner:e.owner}),"media.session.update":L,"media.session.destroy":L,"media.state":L,"media.capabilities":L,"notify.send":e=>m({type:"notify.send.result",id:e.id,notificationId:`notif-${String(e.id)}`}),"notify.dismiss":L,"notify.badge":L,"notify.channel.register":L,"notify.permission.request":e=>m({type:"notify.permission.result",id:e.id,granted:!0}),"config.registerSchema":e=>m({type:"config.registerSchema.result",id:e.id,ok:!0}),"config.get":e=>m({type:"config.values",id:e.id,values:{}}),"config.subscribe":()=>m({type:"config.values",values:{}}),"config.unsubscribe":L,"config.openSettings":L,"resource.bytes":e=>{const t=yt(e.url);return m(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=>m({type:"resource.bytesMany.result",id:e.id,items:Array.isArray(e.urls)?e.urls.map(t=>{const r=yt(t);return{url:t,ok:!0,blob:r?.blob??new Blob([]),mime:r?.mime??"application/octet-stream"}}):[]}),"resource.info":e=>m({type:"resource.info.result",id:e.id,info:{schemes:[{scheme:"data",enabled:!0},{scheme:"https",enabled:!0}]}}),"resource.cancel":L,"cvm.discover":e=>m({type:"cvm.discover.result",id:e.id,servers:[]}),"cvm.request":e=>m({type:"cvm.request.result",id:e.id,message:{}}),"cvm.close":e=>m({type:"cvm.close.result",id:e.id}),"outbox.getEvent":e=>m({type:"outbox.getEvent.result",id:e.id}),"outbox.query":e=>m({type:"outbox.query.result",id:e.id,events:[]}),"outbox.subscribe":e=>m({type:"outbox.closed",subId:e.subId,reason:"reference shell complete"}),"outbox.close":L,"outbox.publish":e=>m({type:"outbox.publish.result",id:e.id,ok:!0}),"outbox.resolveRelays":e=>m({type:"outbox.resolveRelays.result",id:e.id,plan:{}}),"upload.info":e=>m({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=>m({type:"upload.upload.result",id:e.id,result:{url:qn}}),"upload.status":e=>m({type:"upload.status.result",id:e.id,status:{}}),"ble.open":e=>m({type:"ble.open.result",id:e.id,session:{id:"ble-reference",state:"open",device:{id:"reference-device",name:"Reference BLE"}}}),"ble.services":e=>m({type:"ble.services.result",id:e.id,services:[]}),"ble.read":e=>m({type:"ble.read.result",id:e.id,data:[]}),"ble.write":e=>m({type:"ble.write.result",id:e.id}),"ble.subscribe":e=>m({type:"ble.subscribe.result",id:e.id}),"ble.unsubscribe":e=>m({type:"ble.unsubscribe.result",id:e.id}),"ble.close":e=>m({type:"ble.close.result",id:e.id}),"common.encodeNip19":e=>m({type:"common.encodeNip19.result",id:e.id,ok:!0,value:"npub1reference",nip19Type:"npub"}),"common.decodeNip19":e=>m({type:"common.decodeNip19.result",id:e.id,ok:!0,nip19Type:"npub",hex:xe}),"common.getProfile":e=>m({type:"common.getProfile.result",id:e.id,ok:!0,pubkey:xe,profile:null}),"common.follows":e=>m({type:"common.follows.result",id:e.id,ok:!0,pubkeys:[]}),"common.follow":e=>m({type:"common.follow.result",id:e.id,ok:!0}),"common.unfollow":e=>m({type:"common.unfollow.result",id:e.id,ok:!0}),"common.react":e=>m({type:"common.react.result",id:e.id,ok:!0,eventId:"0".repeat(64)}),"common.report":e=>m({type:"common.report.result",id:e.id,ok:!0,eventId:"1".repeat(64)}),"webrtc.open":e=>m({type:"webrtc.open.result",id:e.id,session:{id:"webrtc-reference",scope:{type:"direct",pubkey:xe},channel:"default",state:"connecting"}}),"webrtc.send":e=>m({type:"webrtc.send.result",id:e.id}),"webrtc.close":e=>m({type:"webrtc.close.result",id:e.id}),"link.open":e=>m({type:"link.open.result",id:e.id,status:"opened"}),"count.query":e=>m({type:"count.query.result",id:e.id,ok:!0,count:0}),"lists.supported":e=>m({type:"lists.supported.result",id:e.id,lists:[]}),"lists.add":e=>m({type:"lists.add.result",id:e.id,ok:!0,added:0,skipped:0}),"lists.remove":e=>m({type:"lists.remove.result",id:e.id,ok:!0,removed:0,skipped:0}),"serial.open":e=>m({type:"serial.open.result",id:e.id,session:{id:`serial-${String(e.id)}`,state:"open"}}),"serial.write":e=>m({type:"serial.write.result",id:e.id}),"serial.close":e=>m({type:"serial.close.result",id:e.id})};function Cn(e={}){const t=e.now??(()=>Date.now()),r=[],n=new Map;function i(d,u){const h=n.get(d);if(h){h.push(u);return}n.set(d,[u])}function s(d){const u=n.get(d)??[];return n.delete(d),u}function o(d,u){return m({type:"intent.invoke.result",id:d,result:{ok:!1,error:u}})}function c(d,u){const h=u.request;if(typeof h!="object"||h===null||Array.isArray(h))return o(u.id,"invalid intent request");const b=h,{archetype:w,action:I,convention:T}=b;if(typeof w!="string"||typeof I!="string"||typeof T!="string")return o(u.id,"intent request must carry normalized identity");const j=/^napplet:([^/?#\s]+)\/([^/?#\s]+)$/.exec(T);if(!j||j[1]!==w||j[2]!==I)return o(u.id,"intent request conflicts with its convention");if(T!==$e)return o(u.id,"no reference handler for convention");const W={sender:d.dTag,archetype:w,action:I,convention:T};return"payload"in b&&(W.payload=b.payload),i(je,{type:"intent.deliver",delivery:W}),m({type:"intent.invoke.result",id:u.id,result:{ok:!0,archetype:w,action:I,convention:T,handler:je}})}function a(d){return d!=="note"?{archetype:d,available:!1,candidates:[],hasDefault:!1}:{archetype:d,available:!0,candidates:[{dTag:je,actions:["open"],conventions:[$e],contracts:[Pn],isDefault:!0}],hasDefault:!0}}function l(d,u){const h=u&&typeof u=="object"&&typeof u.type=="string"?u.type:void 0,b=Tn(u);if(r.push({envelope:u,verdict:b,timestamp:t()}),!h||!b.ok)return[];const w=u;if(h==="intent.invoke")return c(d,w);if(h==="intent.available")return m({type:"intent.available.result",id:w.id,availability:a(w.archetype)});if(h==="intent.handlers")return m({type:"intent.handlers.result",id:w.id,handlers:[a("note")]});if(h==="inc.emit"){if(w.topic===$e){const T={type:"inc.event",topic:w.topic,sender:d.dTag};"payload"in w&&(T.payload=w.payload),i(Ln,T)}return[]}const I=_n[h];return I?I(w):[]}function p(d){return l(Nn,d)}return{get records(){return r},handle:p,handleFrom:l,takeDeliveries:s,reset(){r.length=0,n.clear()}}}function Un(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 Vt="__nappletConformance.error";function wt(e){return new Promise(t=>setTimeout(t,e))}function Hn(e){return e.replace(/&/g,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function jn(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: '${Vt}', 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 Dn(e,t,r){const n=`<base href="${Hn(t)}">`,i=`<script>${jn(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 Mn(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 Zn(e){if(e==="about:blank")return"<!doctype html><html><head></head><body></body></html>";const t=Mn(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 vt(e,t,r,n){const i=Cn(),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=p=>{if(p.source!==s.contentWindow)return;const d=p.data;if(d&&typeof d=="object"&&d.type===Vt){o=`napplet boot error: ${String(d.reason??"unknown error")}`;return}const u=i.handle(p.data),h=p.source;for(const b of u)h?.postMessage(b,"*")};let a=!1;const l=new Promise(p=>{s.addEventListener("load",()=>{a=!0,p()},{once:!0})});n.addEventListener("message",c),(r.body??r.documentElement).appendChild(s);try{const p=await Zn(t.url);s.srcdoc=Dn(p,t.url,e)}catch(p){o=p instanceof Error?p.message:String(p)}if(!o){const p=Symbol("timeout");await Promise.race([l.then(()=>"loaded"),wt(t.readyTimeoutMs).then(()=>p)])===p||!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 wt(t.settleMs)}n.removeEventListener("message",c);try{s.remove()}catch{}return{installedGlobal:o===null,bootError:o,emitted:[...i.records]}}async function Vn(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 vt(ot,n,t,r);let s=null;{const o=await vt([],n,t,r);s={bootError:o.bootError,emitted:o.emitted}}return{installedGlobal:i.installedGlobal,bootError:i.bootError,emitted:i.emitted,degraded:s}}var Wn=e=>({status:"pass",detail:e}),Fn=(e,t)=>({status:"fail",detail:e,diagnostics:t}),Kn=e=>({status:"skip",detail:e}),q={pass:Wn,fail:Fn,skip:Kn};function Yn(e,t){return ct(e.manifestEvent).errors.filter(r=>t.includes(r.code))}function De(e,t,r){return{id:e,area:"manifest",severity:"error",title:t,run:n=>{if(!n.manifestEvent)return q.skip("No NIP-5D manifest event was resolved");const i=Yn(n,r);return i.length?q.fail(i[0].message,i):q.pass()}}}var zn=[De("manifest/event-kind","Resolved event is a NIP-5D napplet manifest",["invalid-napplet-kind","missing-d-tag","unexpected-d-tag"]),De("manifest/index-html","Manifest declares a hashed /index.html",["missing-index-html","invalid-index-html-hash"]),De("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?q.fail("Napplet relied on allow-same-origin, which a shell must not grant"):q.pass():q.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?q.pass():q.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?q.fail(e.bootError):q.pass()},{id:"boot/no-forbidden-globals",area:"boot",severity:"error",title:"Does not access forbidden browser authority surfaces",run:e=>e.forbiddenGlobals.length?q.fail(`Accessed forbidden surface(s): ${e.forbiddenGlobals.join(", ")}`,e.forbiddenGlobals):q.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 q.skip("Napplet emitted no envelopes");const t=e.emitted.filter(n=>!n.verdict.ok);if(t.length===0)return q.pass(`${e.emitted.length} envelope(s) valid`);const r=t.map(n=>({envelope:n.envelope,errors:n.verdict.errors}));return q.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?q.fail(`Crashed with no injected domains: ${e.degraded.bootError}`):q.pass():q.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?q.fail("Message listeners remained after unload"):q.pass():q.skip("Lifecycle not measured")}];function Gn(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?Zt(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 Xn(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name==="Uint8Array"}function lt(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=Xn(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 Et(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 Jn(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 Xe(...e){for(let t=0;t<e.length;t++)e[t].fill(0)}function Me(e){return new DataView(e.buffer,e.byteOffset,e.byteLength)}function G(e,t){return e<<32-t|e>>>t}const Wt=typeof Uint8Array.from([]).toHex=="function"&&typeof Uint8Array.fromHex=="function",Qn=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));function _(e){if(M(e),Wt)return e.toHex();let t="";for(let r=0;r<e.length;r++)t+=Qn[e[r]];return t}const Q={_0:48,_9:57,A:65,F:70,a:97,f:102};function xt(e){if(e>=Q._0&&e<=Q._9)return e-Q._0;if(e>=Q.A&&e<=Q.F)return e-(Q.A-10);if(e>=Q.a&&e<=Q.f)return e-(Q.a-10)}function J(e){if(typeof e!="string")throw new Error("hex string expected, got "+typeof e);if(Wt)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=xt(e.charCodeAt(s)),c=xt(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 Ae(...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 er(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 Ft(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 tr=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 Kt(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&e.constructor.name==="Uint8Array"}function Yt(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 nr(e){if(typeof e!="function")throw new Error("function expected");return!0}function be(e,t){if(typeof t!="string")throw new Error(`${e}: string expected`);return!0}function zt(e){if(!Number.isSafeInteger(e))throw new Error(`invalid integer: ${e}`)}function Je(e){if(!Array.isArray(e))throw new Error("array expected")}function Gt(e,t){if(!Yt(!0,t))throw new Error(`${e}: array of strings expected`)}function Xt(e,t){if(!Yt(!1,t))throw new Error(`${e}: array of numbers expected`)}function rr(...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 ir(e){const t=typeof e=="string"?e.split(""):e,r=t.length;Gt("alphabet",t);const n=new Map(t.map((i,s)=>[i,s]));return{encode:i=>(Je(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=>(Je(i),i.map(s=>{be("alphabet.decode",s);const o=n.get(s);if(o===void 0)throw new Error(`Unknown letter: "${s}". Allowed: ${e}`);return o}))}}function sr(e=""){return be("join",e),{encode:t=>(Gt("join.decode",t),t.join(e)),decode:t=>(be("join.decode",t),t.split(e))}}const Jt=(e,t)=>t===0?e:Jt(t,e%t),Be=(e,t)=>e+(t-Jt(e,t)),Ie=(()=>{let e=[];for(let t=0;t<40;t++)e.push(2**t);return e})();function Qe(e,t,r,n){if(Je(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(Be(t,r)>32)throw new Error(`convertRadix2: carry overflow from=${t} to=${r} carryBits=${Be(t,r)}`);let i=0,s=0;const o=Ie[t],c=Ie[r]-1,a=[];for(const l of e){if(zt(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 p=Ie[s];if(p===void 0)throw new Error("invalid carry");i&=p-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 or(e,t=!1){if(zt(e),e<=0||e>32)throw new Error("radix2: bits should be in (0..32]");if(Be(8,e)>32||Be(e,8)>32)throw new Error("radix2: carry overflow");return{encode:r=>{if(!Kt(r))throw new Error("radix2.encode input should be Uint8Array");return Qe(Array.from(r),8,e,!t)},decode:r=>(Xt("radix2.decode",r),Uint8Array.from(Qe(r,e,8,t)))}}function kt(e){return nr(e),function(...t){try{return e.apply(null,t)}catch{}}}const et=rr(ir("qpzry9x8gf2tvdw0s3jn54khce6mua7l"),sr("")),Rt=[996825010,642813549,513874426,1027748829,705979059];function fe(e){const t=e>>25;let r=(e&33554431)<<5;for(let n=0;n<Rt.length;n++)(t>>n&1)===1&&(r^=Rt[n]);return r}function St(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,et.encode(Qe([i%Ie[30]],30,5,!1))}function ar(e){const t=e==="bech32"?1:734539939,r=or(5),n=r.decode,i=r.encode,s=kt(n);function o(d,u,h=90){be("bech32.encode prefix",d),Kt(u)&&(u=Array.from(u)),Xt("bech32.encode",u);const b=d.length;if(b===0)throw new TypeError(`Invalid prefix length ${b}`);const w=b+7+u.length;if(h!==!1&&w>h)throw new TypeError(`Length ${w} exceeds limit ${h}`);const I=d.toLowerCase(),T=St(I,u,t);return`${I}1${et.encode(u)}${T}`}function c(d,u=90){be("bech32.decode input",d);const h=d.length;if(h<8||u!==!1&&h>u)throw new TypeError(`invalid string length: ${h} (${d}). Expected (8..${u})`);const b=d.toLowerCase();if(d!==b&&d!==d.toUpperCase())throw new Error("String must be lowercase or uppercase");const w=b.lastIndexOf("1");if(w===0||w===-1)throw new Error('Letter "1" must be present between prefix and data only');const I=b.slice(0,w),T=b.slice(w+1);if(T.length<6)throw new Error("Data must be at least 6 characters long");const j=et.decode(T).slice(0,-6),W=St(I,j,t);if(!T.endsWith(W))throw new Error(`Invalid checksum in ${d}: expected "${W}"`);return{prefix:I,words:j}}const a=kt(c);function l(d){const{prefix:u,words:h}=c(d,!1);return{prefix:u,words:h,bytes:n(h)}}function p(d,u){return o(d,i(u))}return{encode:o,decode:c,encodeFromBytes:p,decodeToBytes:l,decodeUnsafe:a,fromWords:n,fromWordsUnsafe:s,toWords:i}}const $t=ar("bech32");var ke=new TextDecoder("utf-8");new TextEncoder;var cr=5e3;function lr(e){try{return e.startsWith("nostr:")&&(e=e.substring(6)),dr(e)}catch{return{type:"invalid",data:null}}}function dr(e){let{prefix:t,words:r}=$t.decode(e,cr),n=new Uint8Array($t.fromWords(r));switch(t){case"nprofile":{let i=Ze(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=Ze(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=Ze(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 Ze(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 ur(e,t,r){return e&t^~e&r}function fr(e,t,r){return e&t^e&r^t&r}class hr{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=Me(this.buffer)}update(t){Et(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=Me(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){Et(this),Jn(t,this),this.finished=!0;const{buffer:r,view:n,blockLen:i,isLE:s}=this;let{pos:o}=this;r[o++]=128,Xe(this.buffer.subarray(o)),this.padOffset>i-o&&(this.process(n,0),o=0);for(let d=o;d<i;d++)r[d]=0;n.setBigUint64(i-8,BigInt(this.length*8),s),this.process(n,0);const c=Me(t),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen must be aligned to 32bit");const l=a/4,p=this.get();if(l>p.length)throw new Error("_sha2: outputLen bigger than state");for(let d=0;d<l;d++)c.setUint32(4*d,p[d],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]),pr=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 gr extends hr{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 d=0;d<16;d++,r+=4)te[d]=t.getUint32(r,!1);for(let d=16;d<64;d++){const u=te[d-15],h=te[d-2],b=G(u,7)^G(u,18)^u>>>3,w=G(h,17)^G(h,19)^h>>>10;te[d]=w+te[d-7]+b+te[d-16]|0}let{A:n,B:i,C:s,D:o,E:c,F:a,G:l,H:p}=this;for(let d=0;d<64;d++){const u=G(c,6)^G(c,11)^G(c,25),h=p+u+ur(c,a,l)+pr[d]+te[d]|0,w=(G(n,2)^G(n,13)^G(n,22))+fr(n,i,s)|0;p=l,l=a,a=c,c=o+h|0,o=s,s=i,i=n,n=h+w|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,p=p+this.H|0,this.set(n,i,s,o,c,a,l,p)}roundClean(){Xe(te)}destroy(){this.set(0,0,0,0,0,0,0,0),Xe(this.buffer)}}class mr extends gr{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 Oe=er(()=>new mr,tr(1));/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const dt=BigInt(0),tt=BigInt(1);function It(e,t=""){if(typeof e!="boolean"){const r=t&&`"${t}" `;throw new Error(r+"expected boolean, got type="+typeof e)}return e}function br(e){if(typeof e=="bigint"){if(!vr(e))throw new Error("positive bigint expected, got "+e)}else lt(e);return e}function Qt(e){if(typeof e!="string")throw new Error("hex string expected, got "+typeof e);return e===""?dt:BigInt("0x"+e)}function ut(e){return Qt(_(e))}function en(e){return Qt(_(yr(M(e)).reverse()))}function ft(e,t){lt(t),e=br(e);const r=J(e.toString(16).padStart(t*2,"0"));if(r.length!==t)throw new Error("number too large");return r}function tn(e,t){return ft(e,t).reverse()}function yr(e){return Uint8Array.from(e)}function wr(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 vr=e=>typeof e=="bigint"&&dt<=e;function Er(e){let t;for(t=0;e>dt;e>>=tt,t+=1);return t}const nn=e=>(tt<<BigInt(e))-tt;function rn(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 Tt(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 D=BigInt(0),H=BigInt(1),se=BigInt(2),sn=BigInt(3),on=BigInt(4),an=BigInt(5),xr=BigInt(7),cn=BigInt(8),kr=BigInt(9),ln=BigInt(16);function Y(e,t){const r=e%t;return r>=D?r:t+r}function V(e,t,r){let n=e;for(;t-- >D;)n*=n,n%=r;return n}function At(e,t){if(e===D)throw new Error("invert: expected non-zero number");if(t<=D)throw new Error("invert: expected positive modulus, got "+t);let r=Y(e,t),n=t,i=D,s=H;for(;r!==D;){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 Y(i,t)}function ht(e,t,r){if(!e.eql(e.sqr(t),r))throw new Error("Cannot find square root")}function dn(e,t){const r=(e.ORDER+H)/on,n=e.pow(t,r);return ht(e,n,t),n}function Rr(e,t){const r=(e.ORDER-an)/cn,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 ht(e,c,t),c}function Sr(e){const t=Pe(e),r=un(e),n=r(t,t.neg(t.ONE)),i=r(t,n),s=r(t,t.neg(n)),o=(e+xr)/ln;return(c,a)=>{let l=c.pow(a,o),p=c.mul(l,n);const d=c.mul(l,i),u=c.mul(l,s),h=c.eql(c.sqr(p),a),b=c.eql(c.sqr(d),a);l=c.cmov(l,p,h),p=c.cmov(u,d,b);const w=c.eql(c.sqr(p),a),I=c.cmov(l,p,w);return ht(c,I,a),I}}function un(e){if(e<sn)throw new Error("sqrt is not defined for small field");let t=e-H,r=0;for(;t%se===D;)t/=se,r++;let n=se;const i=Pe(e);for(;Bt(i,n)===1;)if(n++>1e3)throw new Error("Cannot find square root: probably non-prime P");if(r===1)return dn;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 p=r,d=a.mul(a.ONE,s),u=a.pow(l,t),h=a.pow(l,o);for(;!a.eql(u,a.ONE);){if(a.is0(u))return a.ZERO;let b=1,w=a.sqr(u);for(;!a.eql(w,a.ONE);)if(b++,w=a.sqr(w),b===p)throw new Error("Cannot find square root");const I=H<<BigInt(p-b-1),T=a.pow(d,I);p=b,d=a.sqr(T),u=a.mul(u,d),h=a.mul(h,T)}return h}}function $r(e){return e%on===sn?dn:e%cn===an?Rr:e%ln===kr?Sr(e):un(e)}const Ir=["create","isValid","is0","neg","inv","sqrt","sqr","eql","add","sub","mul","pow","div","addN","subN","mulN","sqrN"];function Tr(e){const t={ORDER:"bigint",BYTES:"number",BITS:"number"},r=Ir.reduce((n,i)=>(n[i]="function",n),t);return rn(e,r),e}function Ar(e,t,r){if(r<D)throw new Error("invalid exponent, negatives unsupported");if(r===D)return e.ONE;if(r===H)return t;let n=e.ONE,i=t;for(;r>D;)r&H&&(n=e.mul(n,i)),i=e.sqr(i),r>>=H;return n}function fn(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 Br(e,t){t!==void 0&&lt(t);const r=t!==void 0?t:e.toString(2).length,n=Math.ceil(r/8);return{nBitLength:r,nByteLength:n}}class Or{ORDER;BITS;BYTES;isLE;ZERO=D;ONE=H;_lengths;_sqrt;_mod;constructor(t,r={}){if(t<=D)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}=Br(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 Y(t,this.ORDER)}isValid(t){if(typeof t!="bigint")throw new Error("invalid field element: expected bigint, got "+typeof t);return D<=t&&t<this.ORDER}is0(t){return t===D}isValidNot0(t){return!this.is0(t)&&this.isValid(t)}isOdd(t){return(t&H)===H}neg(t){return Y(-t,this.ORDER)}eql(t,r){return t===r}sqr(t){return Y(t*t,this.ORDER)}add(t,r){return Y(t+r,this.ORDER)}sub(t,r){return Y(t-r,this.ORDER)}mul(t,r){return Y(t*r,this.ORDER)}pow(t,r){return Ar(this,t,r)}div(t,r){return Y(t*At(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 At(t,this.ORDER)}sqrt(t){return this._sqrt||(this._sqrt=$r(this.ORDER)),this._sqrt(this,t)}toBytes(t){return this.isLE?tn(t,this.BYTES):ft(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?en(t):ut(t);if(c&&(a=Y(a,o)),!r&&!this.isValid(a))throw new Error("invalid field element: outside of range 0..ORDER");return a}invertBatch(t){return fn(this,t)}cmov(t,r,n){return n?r:t}}function Pe(e,t={}){return new Or(e,t)}function hn(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 Nr(e){const t=hn(e);return t+Math.ceil(t/2)}function qr(e,t,r=!1){M(e);const n=e.length,i=hn(t),s=Nr(t);if(n<16||n<s||n>1024)throw new Error("expected "+s+"-1024 bytes of input, got "+n);const o=r?en(e):ut(e),c=Y(o,t-H)+H;return r?tn(c,i):ft(c,i)}/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const le=BigInt(0),oe=BigInt(1);function Ne(e,t){const r=t.negate();return e?r:t}function Ot(e,t){const r=fn(e.Fp,t.map(n=>n.Z));return t.map((n,i)=>e.fromAffine(n.toAffine(r[i])))}function pn(e,t){if(!Number.isSafeInteger(e)||e<=0||e>t)throw new Error("invalid window size, expected [1.."+t+"], got W="+e)}function Ve(e,t){pn(e,t);const r=Math.ceil(t/e)+1,n=2**(e-1),i=2**e,s=nn(e),o=BigInt(e);return{windows:r,windowSize:n,mask:s,maxNumber:i,shiftBy:o}}function Nt(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,p=l+Math.abs(c)-1,d=c===0,u=c<0,h=t%2!==0;return{nextN:a,offset:p,isZero:d,isNeg:u,isNegF:h,offsetF:l}}const We=new WeakMap,gn=new WeakMap;function Fe(e){return gn.get(e)||1}function qt(e){if(e!==le)throw new Error("invalid wNAF")}class Lr{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}=Ve(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=Ve(t,this.bits);for(let c=0;c<o.windows;c++){const{nextN:a,offset:l,isZero:p,isNeg:d,isNegF:u,offsetF:h}=Nt(n,c,o);n=a,p?s=s.add(Ne(u,r[h])):i=i.add(Ne(d,r[l]))}return qt(n),{p:i,f:s}}wNAFUnsafe(t,r,n,i=this.ZERO){const s=Ve(t,this.bits);for(let o=0;o<s.windows&&n!==le;o++){const{nextN:c,offset:a,isZero:l,isNeg:p}=Nt(n,o,s);if(n=c,!l){const d=r[a];i=i.add(p?d.negate():d)}}return qt(n),i}getPrecomputes(t,r,n){let i=We.get(r);return i||(i=this.precomputeWindow(r,t),t!==1&&(typeof n=="function"&&(i=n(i)),We.set(r,i))),i}cached(t,r,n){const i=Fe(t);return this.wNAF(i,this.getPrecomputes(i,t,n),r)}unsafe(t,r,n,i){const s=Fe(t);return s===1?this._unsafeLadder(t,r,i):this.wNAFUnsafe(s,this.getPrecomputes(s,t,n),r,i)}createCache(t,r){pn(r,this.bits),gn.set(t,r),We.delete(t)}hasCache(t){return Fe(t)!==1}}function Pr(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 Lt(e,t,r){if(t){if(t.ORDER!==e)throw new Error("Field.ORDER must match order: Fp == p, Fn == n");return Tr(t),t}else return Pe(e,{isLE:r})}function _r(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=Lt(t.p,r.Fp,n),s=Lt(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 Cr(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 Pt=(e,t)=>(e+(e>=0?t:-t)/Hr)/t;function Ur(e,t,r){const[[n,i],[s,o]]=t,c=Pt(o*e,r),a=Pt(-i*e,r);let l=e-c*n-a*s,p=-c*i-a*o;const d=l<he,u=p<he;d&&(l=-l),u&&(p=-p);const h=nn(Math.ceil(Er(r)/2))+Te;if(l<he||l>=h||p<he||p>=h)throw new Error("splitScalar (endomorphism): failed, k="+e);return{k1neg:d,k1:l,k2neg:u,k2:p}}const he=BigInt(0),Te=BigInt(1),Hr=BigInt(2),Re=BigInt(3),jr=BigInt(4);function Dr(e,t={}){const r=_r("weierstrass",e,t),{Fp:n,Fn:i}=r;let s=r.CURVE;const{h:o,n:c}=s;rn(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=Zr(n,i);function p(){if(!n.isOdd)throw new Error("compression is not supported: Field does not have .isOdd()")}function d(A,g,y){const{x:E,y:x}=g.toAffine(),B=n.toBytes(E);if(It(y,"isCompressed"),y){p();const S=!n.isOdd(x);return Ae(Mr(S),B)}else return Ae(Uint8Array.of(4),B,n.toBytes(x))}function u(A){M(A,void 0,"Point");const{publicKey:g,publicKeyUncompressed:y}=l,E=A.length,x=A[0],B=A.subarray(1);if(E===g&&(x===2||x===3)){const S=n.fromBytes(B);if(!n.isValid(S))throw new Error("bad point: is not on curve, wrong x");const k=w(S);let v;try{v=n.sqrt(k)}catch(Z){const P=Z instanceof Error?": "+Z.message:"";throw new Error("bad point: is not on curve, sqrt error"+P)}p();const R=n.isOdd(v);return(x&1)===1!==R&&(v=n.neg(v)),{x:S,y:v}}else if(E===y&&x===4){const S=n.BYTES,k=n.fromBytes(B.subarray(0,S)),v=n.fromBytes(B.subarray(S,S*2));if(!I(k,v))throw new Error("bad point: is not on curve");return{x:k,y:v}}else throw new Error(`bad point: got length ${E}, expected compressed=${g} or uncompressed=${y}`)}const h=t.toBytes||d,b=t.fromBytes||u;function w(A){const g=n.sqr(A),y=n.mul(g,A);return n.add(n.add(y,n.mul(A,s.a)),s.b)}function I(A,g){const y=n.sqr(g),E=w(A);return n.eql(y,E)}if(!I(s.Gx,s.Gy))throw new Error("bad curve params: generator point");const T=n.mul(n.pow(s.a,Re),jr),j=n.mul(n.sqr(s.b),BigInt(27));if(n.is0(n.add(T,j)))throw new Error("bad curve params: a or b");function W(A,g,y=!1){if(!n.isValid(g)||y&&n.is0(g))throw new Error(`bad point coordinate ${A}`);return g}function we(A){if(!(A instanceof N))throw new Error("Weierstrass Point expected")}function ve(A){if(!a||!a.basises)throw new Error("no endo");return Ur(A,a.basises,i.ORDER)}const Ee=Tt((A,g)=>{const{X:y,Y:E,Z:x}=A;if(n.eql(x,n.ONE))return{x:y,y:E};const B=A.is0();g==null&&(g=B?n.ONE:n.inv(x));const S=n.mul(y,g),k=n.mul(E,g),v=n.mul(x,g);if(B)return{x:n.ZERO,y:n.ZERO};if(!n.eql(v,n.ONE))throw new Error("invZ was invalid");return{x:S,y:k}}),Rn=Tt(A=>{if(A.is0()){if(t.allowInfinityPoint&&!n.is0(A.Y))return;throw new Error("bad point: ZERO")}const{x:g,y}=A.toAffine();if(!n.isValid(g)||!n.isValid(y))throw new Error("bad point: x or y not field elements");if(!I(g,y))throw new Error("bad point: equation left != right");if(!A.isTorsionFree())throw new Error("bad point: not in prime-order subgroup");return!0});function mt(A,g,y,E,x){return y=new N(n.mul(y.X,A),y.Y,y.Z),g=Ne(E,g),y=Ne(x,y),g.add(y)}class N{static BASE=new N(s.Gx,s.Gy,n.ONE);static ZERO=new N(n.ZERO,n.ONE,n.ZERO);static Fp=n;static Fn=i;X;Y;Z;constructor(g,y,E){this.X=W("x",g),this.Y=W("y",y,!0),this.Z=W("z",E),Object.freeze(this)}static CURVE(){return s}static fromAffine(g){const{x:y,y:E}=g||{};if(!g||!n.isValid(y)||!n.isValid(E))throw new Error("invalid affine point");if(g instanceof N)throw new Error("projective point not allowed");return n.is0(y)&&n.is0(E)?N.ZERO:new N(y,E,n.ONE)}static fromBytes(g){const y=N.fromAffine(b(M(g,void 0,"point")));return y.assertValidity(),y}static fromHex(g){return N.fromBytes(J(g))}get x(){return this.toAffine().x}get y(){return this.toAffine().y}precompute(g=8,y=!0){return ue.createCache(this,g),y||this.multiply(Re),this}assertValidity(){Rn(this)}hasEvenY(){const{y:g}=this.toAffine();if(!n.isOdd)throw new Error("Field doesn't support isOdd");return!n.isOdd(g)}equals(g){we(g);const{X:y,Y:E,Z:x}=this,{X:B,Y:S,Z:k}=g,v=n.eql(n.mul(y,k),n.mul(B,x)),R=n.eql(n.mul(E,k),n.mul(S,x));return v&&R}negate(){return new N(this.X,n.neg(this.Y),this.Z)}double(){const{a:g,b:y}=s,E=n.mul(y,Re),{X:x,Y:B,Z:S}=this;let k=n.ZERO,v=n.ZERO,R=n.ZERO,$=n.mul(x,x),Z=n.mul(B,B),P=n.mul(S,S),O=n.mul(x,B);return O=n.add(O,O),R=n.mul(x,S),R=n.add(R,R),k=n.mul(g,R),v=n.mul(E,P),v=n.add(k,v),k=n.sub(Z,v),v=n.add(Z,v),v=n.mul(k,v),k=n.mul(O,k),R=n.mul(E,R),P=n.mul(g,P),O=n.sub($,P),O=n.mul(g,O),O=n.add(O,R),R=n.add($,$),$=n.add(R,$),$=n.add($,P),$=n.mul($,O),v=n.add(v,$),P=n.mul(B,S),P=n.add(P,P),$=n.mul(P,O),k=n.sub(k,$),R=n.mul(P,Z),R=n.add(R,R),R=n.add(R,R),new N(k,v,R)}add(g){we(g);const{X:y,Y:E,Z:x}=this,{X:B,Y:S,Z:k}=g;let v=n.ZERO,R=n.ZERO,$=n.ZERO;const Z=s.a,P=n.mul(s.b,Re);let O=n.mul(y,B),F=n.mul(E,S),K=n.mul(x,k),ae=n.add(y,E),U=n.add(B,S);ae=n.mul(ae,U),U=n.add(O,F),ae=n.sub(ae,U),U=n.add(y,x);let z=n.add(B,k);return U=n.mul(U,z),z=n.add(O,K),U=n.sub(U,z),z=n.add(E,x),v=n.add(S,k),z=n.mul(z,v),v=n.add(F,K),z=n.sub(z,v),$=n.mul(Z,U),v=n.mul(P,K),$=n.add(v,$),v=n.sub(F,$),$=n.add(F,$),R=n.mul(v,$),F=n.add(O,O),F=n.add(F,O),K=n.mul(Z,K),U=n.mul(P,U),F=n.add(F,K),K=n.sub(O,K),K=n.mul(Z,K),U=n.add(U,K),O=n.mul(F,U),R=n.add(R,O),O=n.mul(z,U),v=n.mul(ae,v),v=n.sub(v,O),O=n.mul(ae,F),$=n.mul(z,$),$=n.add($,O),new N(v,R,$)}subtract(g){return this.add(g.negate())}is0(){return this.equals(N.ZERO)}multiply(g){const{endo:y}=t;if(!i.isValidNot0(g))throw new Error("invalid scalar: out of range");let E,x;const B=S=>ue.cached(this,S,k=>Ot(N,k));if(y){const{k1neg:S,k1:k,k2neg:v,k2:R}=ve(g),{p:$,f:Z}=B(k),{p:P,f:O}=B(R);x=Z.add(O),E=mt(y.beta,$,P,S,v)}else{const{p:S,f:k}=B(g);E=S,x=k}return Ot(N,[E,x])[0]}multiplyUnsafe(g){const{endo:y}=t,E=this;if(!i.isValid(g))throw new Error("invalid scalar: out of range");if(g===he||E.is0())return N.ZERO;if(g===Te)return E;if(ue.hasCache(this))return this.multiply(g);if(y){const{k1neg:x,k1:B,k2neg:S,k2:k}=ve(g),{p1:v,p2:R}=Pr(N,E,B,k);return mt(y.beta,v,R,x,S)}else return ue.unsafe(E,g)}toAffine(g){return Ee(this,g)}isTorsionFree(){const{isTorsionFree:g}=t;return o===Te?!0:g?g(N,this):ue.unsafe(this,c).is0()}clearCofactor(){const{clearCofactor:g}=t;return o===Te?this:g?g(N,this):this.multiplyUnsafe(o)}isSmallOrder(){return this.multiplyUnsafe(o).is0()}toBytes(g=!0){return It(g,"isCompressed"),this.assertValidity(),h(N,this,g)}toHex(g=!0){return _(this.toBytes(g))}toString(){return`<Point ${this.is0()?"ZERO":this.toHex()}>`}}const bt=i.BITS,ue=new Lr(N,t.endo?Math.ceil(bt/2):bt);return N.BASE.precompute(8),N}function Mr(e){return Uint8Array.of(e?2:3)}function Zr(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 _e={p:BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),n:BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),h:BigInt(1),a:BigInt(0),b:BigInt(7),Gx:BigInt("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),Gy:BigInt("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")},Vr={beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),basises:[[BigInt("0x3086d221a7d46bcde86c90e49284eb15"),-BigInt("0xe4437ed6010e88286f547fa90abfe4c3")],[BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),BigInt("0x3086d221a7d46bcde86c90e49284eb15")]]},Wr=BigInt(0),nt=BigInt(2);function Fr(e){const t=_e.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,p=l*l*e%t,d=V(p,r,t)*p%t,u=V(d,r,t)*p%t,h=V(u,nt,t)*l%t,b=V(h,i,t)*h%t,w=V(b,s,t)*b%t,I=V(w,c,t)*w%t,T=V(I,a,t)*I%t,j=V(T,c,t)*w%t,W=V(j,r,t)*p%t,we=V(W,o,t)*b%t,ve=V(we,n,t)*l%t,Ee=V(ve,nt,t);if(!qe.eql(qe.sqr(Ee),e))throw new Error("Cannot find square root");return Ee}const qe=Pe(_e.p,{sqrt:Fr}),de=Dr(_e,{Fp:qe,endo:Vr}),_t={};function Le(e,...t){let r=_t[e];if(r===void 0){const n=Oe(wr(e));r=Ae(n,n),_t[e]=r}return Oe(Ae(r,...t))}const pt=e=>e.toBytes(!0).slice(1),gt=e=>e%nt===Wr;function rt(e){const{Fn:t,BASE:r}=de,n=t.fromBytes(e),i=r.multiply(n);return{scalar:gt(i.y)?n:t.neg(n),bytes:pt(i)}}function mn(e){const t=qe;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);gt(i)||(i=t.neg(i));const s=de.fromAffine({x:e,y:i});return s.assertValidity(),s}const me=ut;function bn(...e){return de.Fn.create(me(Le("BIP0340/challenge",...e)))}function Ct(e){return rt(e).bytes}function Kr(e,t,r=Ft(32)){const{Fn:n}=de,i=M(e,void 0,"message"),{bytes:s,scalar:o}=rt(t),c=M(r,32,"auxRand"),a=n.toBytes(o^me(Le("BIP0340/aux",c))),l=Le("BIP0340/nonce",a,s,i),{bytes:p,scalar:d}=rt(l),u=bn(p,s,i),h=new Uint8Array(64);if(h.set(p,0),h.set(n.toBytes(n.create(d+u*o)),32),!yn(h,i,s))throw new Error("sign: Invalid signature produced");return h}function yn(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=mn(me(a)),p=me(o.subarray(0,32));if(!n.isValidNot0(p))return!1;const d=me(o.subarray(32,64));if(!i.isValidNot0(d))return!1;const u=bn(i.toBytes(p),pt(l),c),h=s.multiplyUnsafe(d).add(l.multiplyUnsafe(i.neg(u))),{x:b,y:w}=h.toAffine();return!(h.is0()||!gt(w)||b!==p)}catch{return!1}}const X=(()=>{const r=(n=Ft(48))=>qr(n,_e.n);return{keygen:Cr(r,Ct),getPublicKey:Ct,sign:Kr,verify:yn,Point:de,utils:{randomSecretKey:r,taggedHash:Le,lift_x:mn,pointToBytes:pt},lengths:{secretKey:32,publicKey:32,publicKeyHasPrefix:!1,signature:64,seed:48}}})();var ie=Symbol("verified"),Yr=e=>e instanceof Object;function zr(e){if(!Yr(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 Gr=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 Xr=class{generateSecretKey(){return X.utils.randomSecretKey()}getPublicKey(t){return _(X.getPublicKey(t))}finalizeEvent(t,r){const n=t;return n.pubkey=_(X.getPublicKey(r)),n.id=Ke(n),n.sig=_(X.sign(J(Ke(n)),r)),n[ie]=!0,n}verifyEvent(t){if(typeof t[ie]=="boolean")return t[ie];try{const r=Ke(t);if(r!==t.id)return t[ie]=!1,!1;const n=X.verify(J(t.sig),J(r),J(t.pubkey));return t[ie]=n,n}catch{return t[ie]=!1,!1}}};function Jr(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 Ke(e){let t=Oe(Gr.encode(Jr(e)));return _(t)}var Ce=new Xr;Ce.generateSecretKey;Ce.getPublicKey;Ce.finalizeEvent;var Qr=Ce.verifyEvent,ei=22242;function ti(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 ni(e,t){for(let r=0;r<e.length;r++)if(ti(e[r],t))return!0;return!1}function ri(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 ii(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 si(e,t){return{kind:ei,created_at:Math.floor(Date.now()/1e3),tags:[["relay",e],["challenge",t]],content:""}}var wn=class extends Error{constructor(e,t){super(`Tried to send message '${e} on a closed connection to ${t}.`),this.name="SendingOnClosedConnection"}},vn=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 vn(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 wn(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(si(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 oi(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=ii(t);if(r){const n=this.openSubs.get(r);if(!n)return;const i=ri(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)&&ni(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}}},oi=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 wn))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)}},ai=e=>(e[ie]=!0,!0),ci=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 vn(e,{verifyEvent:this.trustedRelayURLs.has(e)?ai: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 u of e){const{url:h,filter:b}=u;r.has(h)||r.set(h,[]),r.get(h).push(b)}const n=Array.from(r.entries()).map(([u,h])=>({url:u,filters:h}));this.trackRelays&&(t.receivedEvent=(u,h)=>{let b=this.seenOn.get(h);b||(b=new Set,this.seenOn.set(h,b)),b.add(u)});const i=new Set,s=[],o=[];let c=u=>{o[u]||(o[u]=!0,o.filter(h=>h).length===n.length&&(t.oneose?.(),c=()=>{}))};const a=[];let l=(u,h)=>{a[u]||(c(u),a[u]=h,a.filter(b=>b).length===n.length&&(t.onclose?.(a),l=()=>{}))};const p=u=>{if(t.alreadyHaveEvent?.(u))return!0;const h=i.has(u);return i.add(u),h},d=Promise.all(n.map(async({url:u,filters:h},b)=>{if(this.allowConnectingToRelay?.(u,["read",h])===!1){l(b,"connection skipped by allowConnectingToRelay");return}let w;try{w=await this.ensureRelay(u,{connectionTimeout:this.maxWaitForConnection<(t.maxWait||0)?Math.max(t.maxWait*.8,t.maxWait-1e3):this.maxWaitForConnection,abort:t.abort})}catch(T){this.onRelayConnectionFailure?.(u),l(b,T?.message||String(T));return}this.onRelayConnectionSuccess?.(u);let I=w.subscribe(h,{...t,oneose:()=>c(b),onclose:T=>{T.startsWith("auth-required: ")&&t.onauth?w.auth(t.onauth).then(()=>{w.subscribe(h,{...t,oneose:()=>c(b),onclose:j=>{l(b,j)},alreadyHaveEvent:p,eoseTimeout:t.maxWait,abort:t.abort})}).catch(j=>{l(b,`auth was required and attempted, but failed with: ${j}`)}):l(b,T)},alreadyHaveEvent:p,eoseTimeout:t.maxWait,abort:t.abort});s.push(I)}));return{async close(u){await d,s.forEach(h=>{h.close(u)})}}}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}},En;try{En=WebSocket}catch{}var li=class extends ci{constructor(e){super({verifyEvent:Qr,websocketImplementation:En,maxWaitForConnection:3e3,...e})}},ce=Symbol("verified"),di=e=>e instanceof Object;function ui(e){if(!di(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 fi=new TextEncoder,hi=class{generateSecretKey(){return X.utils.randomSecretKey()}getPublicKey(e){return _(X.getPublicKey(e))}finalizeEvent(e,t){const r=e;return r.pubkey=_(X.getPublicKey(t)),r.id=Ye(r),r.sig=_(X.sign(J(Ye(r)),t)),r[ce]=!0,r}verifyEvent(e){if(typeof e[ce]=="boolean")return e[ce];try{const t=Ye(e);if(t!==e.id)return e[ce]=!1,!1;const r=X.verify(J(e.sig),J(t),J(e.pubkey));return e[ce]=r,r}catch{return e[ce]=!1,!1}}};function pi(e){if(!ui(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 Ye(e){let t=Oe(fi.encode(pi(e)));return _(t)}var Ue=new hi;Ue.generateSecretKey;Ue.getPublicKey;Ue.finalizeEvent;var gi=Ue.verifyEvent;const mi=8e3;function bi(e){return typeof e=="number"&&at.includes(e)}function yi(e){try{const t=new URL(e);return t.protocol==="http:"||t.protocol==="https:"}catch{return!1}}function wi(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 vi(e){const t=lr(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=wi(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&&!bi(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?[...at]:[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 Ei(e){return{id:e.id,pubkey:e.pubkey,kind:e.kind,tags:e.tags}}async function xn(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 xi(e){return e.tags.filter(t=>t[0]==="path"&&typeof t[1]=="string"&&typeof t[2]=="string")}async function ki(e){const t=xi(e).map(r=>`${r[2]} ${r[1]}
77
+ `).sort().join("");return xn(t)}async function Ri(e){const t=await ki(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 Si(e){return e.tags.find(t=>t[0]==="path"&&t[1]==="/index.html")}function $i(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 Ii(e,t){const r=e.endsWith("/")?e:`${e}/`;return new URL(t,r).href}async function Ti(e,t=fetch){const n=Si(e)?.[2];if(!n)throw new Error("Manifest does not include a /index.html path hash");const i=$i(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=Ii(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()),p=await xn(l);if(p!==n){s.push(`${c}: sha256 ${p} 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 Ai(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 Bi(e,t){const r=vi(e),n=!t.pool,i=t.pool??new li;try{const s=await i.get(r.relays,{...r.filter},{maxWait:t.maxRelayWaitMs??mi});if(!s)throw new Error("No matching napplet manifest event was found on the pointer relay hints");if(!gi(s))throw new Error("Resolved manifest event signature is invalid");const o=Ei(s),c=ct(o);if(!c.ok)throw new Error(`Resolved event is not a valid NIP-5D napplet manifest: ${c.errors[0]?.message}`);await Ri(o);const a=await Ti(o,t.fetcher??fetch),l=(t.createObjectUrl??URL.createObjectURL)(new Blob([a],{type:"text/html;charset=utf-8"})),p=()=>(t.revokeObjectUrl??URL.revokeObjectURL)(l);return{source:"nostr",input:e,label:o.id??e,bootUrl:l,html:a,fetched:!0,manifestEvent:o,revoke:p}}finally{n&&i.destroy()}}async function Oi(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 yi(r)?Ai(r,t.fetcher??fetch):Bi(r,t)}const Ni=/\bwindow\s*\.\s*nostr\b|\bglobalThis\s*\.\s*nostr\b/,qi="/__conformance__/events",it=new URLSearchParams(location.search),kn=it.get("live")==="1";function ne(e){return String(e).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}const re=document.getElementById("app");function Li(){return re.innerHTML=`
78
+ <header class="masthead">
79
+ <h1>napplet conformance ${kn?'<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=Li();let ye="",st=0;function Se(e,t="info"){C.status.textContent=e,C.status.dataset.kind=t}function ze(e){return String(e).padStart(2,"0")}function Pi(e){const t=new Date(e);return`${ze(t.getHours())}:${ze(t.getMinutes())}:${ze(t.getSeconds())}`}function _i(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>':"",p=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}${p}</li>`}).join("");return`<div class="area"><h3>${ne(o)}</h3><ul>${c}</ul></div>`}).join(""),i=e.summary,s=st>1?`<span class="run-stamp">run #${st} · ${Pi(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 Ci(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 Ui(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=ct(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",Zt(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 Hi(e,t,r){C.output.innerHTML=`
108
+ ${_i(e)}
109
+ <div class="panels">
110
+ <section class="panel"><h2>Emitted envelopes (${t.length})</h2>${Ci(t)}</section>
111
+ <section class="panel"><h2>Manifest</h2>${Ui(r)}</section>
112
+ </div>
113
+ `}async function He(e){ye=e,C.runButton.disabled=!0,C.rerunButton.disabled=!0;let t=null;try{Se(`Resolving ${e} …`),t=await Oi(e);const r=Ni.test(t.html)?["window.nostr"]:[];Se("Booting napplet in a sandboxed iframe …");const n=await Vn({url:t.bootUrl,readyTimeoutMs:5e3,settleMs:800,runDegraded:!0}),i=Un({manifestHtml:t.html,manifestEvent:t.manifestEvent,boot:n,forbiddenGlobals:r}),s=Gn(i);st+=1,Se(s.ok?"Done — conformant.":"Done — non-conformant.",s.ok?"info":"error"),Hi(s,n.emitted,t),C.rerunButton.hidden=!1}catch(r){Se(`Error: ${r instanceof Error?r.message:String(r)}`,"error")}finally{t?.revoke?.(),C.runButton.disabled=!1,C.rerunButton.disabled=!1}}function ji(){if(!kn||typeof EventSource>"u")return;let e=!1;const t=new EventSource(qi);t.addEventListener("rerun",()=>{!ye||e||(e=!0,setTimeout(()=>{e=!1,He(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),He(t)}});C.rerunButton.addEventListener("click",()=>{ye&&He(ye)});ji();const Ge=it.get("target")??it.get("url");Ge&&(C.targetInput.value=Ge,He(Ge));
@@ -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-DZ1po6iW.js"></script>
8
+ <script type="module" crossorigin src="/assets/index-ugli4mLk.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.14",
3
+ "version": "0.2.16",
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.12.3"
18
+ "@napplet/conformance": "^0.14.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.13"
25
+ "@napplet/conformance-web": "0.0.15"
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,"&amp;").replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}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,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;")}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));