@kehto/nip 0.5.2 → 0.7.0

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
@@ -11,7 +11,7 @@ any NIP that isn't referenced.
11
11
  | Subpath | NIP | What it provides |
12
12
  |---------|-----|------------------|
13
13
  | [`@kehto/nip/5a`](./src/5a/README.md) | NIP-5A | aggregate hash compute/verify over `path` tags (`computeAggregateHash`, `verifyAggregate`) |
14
- | [`@kehto/nip/5d`](./src/5d/README.md) | NIP-5D | content-addressed napplet manifest resolution — parse, verify sig + aggregate + Blossom blobs (`resolveNapplet`) with optional Cache Storage artifact caching |
14
+ | [`@kehto/nip/5d`](./src/5d/README.md) | NIP-5D | content-addressed napplet manifest resolution — parse, verify signature + artifact bytes (or legacy aggregate/blobs) (`resolveNapplet`) with optional Cache Storage artifact caching |
15
15
  | [`@kehto/nip/51`](./src/51/README.md) | NIP-51 | lists & sets parser — mute/bookmarks/relay-sets/emoji-sets/… (`parseList`) |
16
16
  | [`@kehto/nip/65`](./src/65/README.md) | NIP-65 | relay-list (kind 10002) parsing + outbox/inbox resolution (`createNip65Registry`) |
17
17
  | [`@kehto/nip/66`](./src/66/README.md) | NIP-66 | kind-30166 relay-discovery aggregator (`createNip66Aggregator`) |
@@ -26,19 +26,21 @@ bytes, see the repo guide:
26
26
 
27
27
  ## NIP-5D archetype manifest contracts
28
28
 
29
- Kehto follows merged [NAP-INTENT at
30
- `5ac0490461ca6fec2f0d2e45b4835cf9bc08de24`](https://github.com/napplet/naps/blob/5ac0490461ca6fec2f0d2e45b4835cf9bc08de24/naps/NAP-INTENT.md).
31
- An accepted manifest tag has the exact form
32
- `["archetype", "<slug>", "napplet:<slug>/<action>"]`.
33
- The convention is stable and queryless; each tag becomes one ordered
34
- archetype/convention declaration. Trailing metadata and numbered NAP names are
35
- rejected.
29
+ Kehto follows [NAP-INTENT PR #106](https://github.com/napplet/naps/blob/fc121fc264615482143eda86125863d2e1f741a2/naps/NAP-INTENT.md) at `fc121fc264615482143eda86125863d2e1f741a2` and NIP-5D PR #2303 at `020cb8b33a9e4c6b8ca4b2f9d0ed0a67843b68f7`.
30
+ Current manifests declare roles with `z` and contracts with
31
+ `["i", "napplet:<role>/<action>", ...parameterNames]`. Only an intent whose
32
+ role is also declared by `z` is eligible. Invalid advertisements are ignored;
33
+ they do not invalidate otherwise valid artifact metadata.
36
34
 
37
- `resolveNapplet()` exposes these verified tags as
38
- `manifest.archetypes: Array<{ slug, convention }>`. The NIP package
39
- parses and verifies that metadata but does not select handlers, retain intent
40
- delivery, or start target windows. Those responsibilities live in
41
- `@kehto/services` and the host runtime.
35
+ `resolveNapplet()` exposes `manifest.archetypes` as ordered
36
+ `{ slug, convention, params }` entries. Parameter names describe text fields,
37
+ not types or required values. Legacy paired archetype tags retain empty params.
38
+ `manifest.catalogId`, also available through `getNappletCatalogId(event)`, is
39
+ publisher/kind-safe: named events use kind, publisher and literal d-tag; roots
40
+ use kind and publisher; snapshots use kind and event ID. Callers treat this ID
41
+ as opaque. The existing dTag/artifact hash remains separate artifact metadata.
42
+
43
+ NIP utilities verify these facts; services and hosts select and run handlers.
42
44
 
43
45
  ## Selection criteria
44
46
 
@@ -87,3 +89,13 @@ Every NIP module is:
87
89
  4. Re-export it from `src/index.ts` for the barrel, and add the
88
90
  `@kehto/nip/<n>` alias to the repo `vitest.config.ts`.
89
91
  5. Add a row to the table above.
92
+
93
+ ## NIP-5D event compatibility
94
+
95
+ Current manifests use a direct artifact `x` hash, plain-text `content`, role-matched
96
+ `z`/`i` intent declarations, and required `R` / optional `O` domains. Kehto also
97
+ accepts legacy aggregate events through an isolated compatibility adapter.
98
+ Existing `aggregateHash` host/cache/ACL fields carry the verified artifact hash
99
+ for current events; legacy identities keep their original aggregate. Both paths
100
+ verify signatures and bytes before runtime injection and `srcdoc` execution.
101
+ For the schema and removal boundary, see [event migration](https://kehto.github.io/web/docs/migrations/NIP-5D-EVENT-SCHEMA.html).
@@ -1,6 +1,16 @@
1
1
  import { NostrEvent } from 'nostr-tools';
2
2
  import { PathEntry } from '../5a/index.js';
3
3
 
4
+ /** NIP-5D napplet manifest event kinds. */
5
+ /** Snapshot manifest — regular event, immutable point-in-time release. */
6
+ declare const NAPPLET_KIND_SNAPSHOT = 5129;
7
+ /** Root manifest — replaceable event, an author's latest unnamed napplet. */
8
+ declare const NAPPLET_KIND_ROOT = 15129;
9
+ /** Named manifest — addressable event (carries a `d` tag identifier). */
10
+ declare const NAPPLET_KIND_NAMED = 35129;
11
+ /** All three NIP-5D napplet manifest kinds. */
12
+ declare const NAPPLET_KINDS: readonly number[];
13
+
4
14
  /** Versioned Cache Storage namespace for verified napplet artifacts. */
5
15
  declare const NAPPLET_ARTIFACT_CACHE_NAME = "kehto:napplet-artifacts:v1";
6
16
  interface NappletCacheDiagnostic {
@@ -233,32 +243,38 @@ declare class CacheStorageNappletArtifactCache implements NappletArtifactCache {
233
243
  */
234
244
  declare function openNappletArtifactCache(options?: OpenNappletArtifactCacheOptions): Promise<CacheStorageNappletArtifactCache | undefined>;
235
245
 
246
+ /** Publisher-safe catalog identities for verified NIP-5D manifest events. */
247
+
236
248
  /**
237
- * `@kehto/nip/5d` — NIP-5D napplet manifest resolution.
249
+ * Derive the runtime catalog identity for a verified NIP-5D manifest event.
238
250
  *
239
- * NIP-5D (`dskvr/nips` branch `nip/5d`) defines content-addressed "napplet"
240
- * web applets published as Nostr events that reference their files by hash
241
- * (NIP-5A `path` tags + an aggregate `x` tag). A runtime resolves a napplet by:
251
+ * Named and root manifests retain a stable publisher-scoped identity while a
252
+ * snapshot is identified by its immutable signed event id. Call this only after
253
+ * the event has passed signature and manifest verification.
242
254
  *
243
- * 1. verifying the manifest event's signature,
244
- * 2. recomputing the NIP-5A aggregate from its `path` tags and checking the
245
- * `["x","<hex>","aggregate"]` tag,
246
- * 3. fetching each referenced blob (e.g. from Blossom) and verifying its hash,
247
- * 4. assembling the verified `/index.html`.
255
+ * @param event - A verified NIP-5D manifest event.
256
+ * @returns The opaque publisher/kind-safe catalog ID.
248
257
  *
249
- * The napplet's identity is the `(dTag, aggregateHash)` tuple **computed** from
250
- * these verified bytes — it is never accepted from a host or gateway.
251
- *
252
- * @module
258
+ * @example
259
+ * ```ts
260
+ * getNappletCatalogId(namedManifest);
261
+ * // → 'nip5d:35129:<publisher>:<literal-d-tag>'
262
+ * ```
253
263
  */
254
- /** Snapshot manifest — regular event, immutable point-in-time release. */
255
- declare const NAPPLET_KIND_SNAPSHOT = 5129;
256
- /** Root manifest — replaceable event, an author's latest unnamed napplet. */
257
- declare const NAPPLET_KIND_ROOT = 15129;
258
- /** Named manifest — addressable event (carries a `d` tag identifier). */
259
- declare const NAPPLET_KIND_NAMED = 35129;
260
- /** All three NIP-5D napplet manifest kinds. */
261
- declare const NAPPLET_KINDS: readonly number[];
264
+ declare function getNappletCatalogId(event: Pick<NostrEvent, 'id' | 'kind' | 'pubkey' | 'tags'>): string;
265
+
266
+ /** Error codes for every napplet resolution failure path. */
267
+ type NappletResolutionErrorCode = 'invalid-signature' | 'invalid-manifest' | 'aggregate-mismatch' | 'blob-hash-mismatch' | 'blob-unavailable' | 'missing-index';
268
+ /**
269
+ * Thrown on any napplet resolution/verification failure. The `code` field
270
+ * identifies which guard rejected, so callers can fail closed without parsing
271
+ * the message.
272
+ */
273
+ declare class NappletResolutionError extends Error {
274
+ readonly code: NappletResolutionErrorCode;
275
+ constructor(code: NappletResolutionErrorCode, message: string);
276
+ }
277
+
262
278
  /**
263
279
  * Whether `kind` is one of the three NIP-5D napplet manifest kinds.
264
280
  *
@@ -268,59 +284,70 @@ declare const NAPPLET_KINDS: readonly number[];
268
284
  declare function isNappletManifestKind(kind: number): boolean;
269
285
  /**
270
286
  * A parsed NIP-5D napplet manifest. All fields are derived from the manifest
271
- * event's tags; the aggregate is the declared `x` tag (verify it separately
272
- * with {@link resolveNapplet} or `@kehto/nip/5a` `verifyAggregate`).
287
+ * event. Declarations are untrusted until {@link resolveNapplet} verifies both
288
+ * the signature and the content bytes.
273
289
  */
274
290
  interface NappletManifest {
291
+ /** Event format selected by the parser. Absent only in older host-created values. */
292
+ format?: 'current' | 'legacy';
293
+ /** Signed artifact hash for current events; legacy aggregate for older events. */
294
+ artifactHash?: string;
295
+ /** Optional domains; absence never prevents loading and presence grants nothing. */
296
+ optional?: string[];
297
+ /** Independent current z declarations, preserved even without accepted intents. */
298
+ archetypeSlugs?: string[];
299
+ /** Accepted queryless intents and advertised parameter names from i tags. */
300
+ intents?: Array<{
301
+ identity: string;
302
+ parameters: string[];
303
+ }>;
304
+ /** Valid supported icon declaration. Hosts may keep generic artwork. */
305
+ icon?: {
306
+ sha256: string;
307
+ mimeType: 'image/png' | 'image/jpeg' | 'image/webp';
308
+ };
309
+ /** Snapshot provenance only; never resolved or trusted on its behalf. */
310
+ parent?: string;
311
+ /** Snapshot root provenance only. */
312
+ root?: string;
275
313
  /** Manifest event kind (`5129` / `15129` / `35129`). */
276
314
  kind: number;
277
315
  /** Author hex public key. */
278
316
  pubkey: string;
279
317
  /** Named-napplet `d` identifier, or `''` for root/snapshot manifests. */
280
318
  dTag: string;
281
- /** File path entries from `path` tags. */
319
+ /** Publisher/kind-safe opaque identity for the verified manifest catalog. */
320
+ catalogId: string;
321
+ /** Normalized files: one /index.html for current events, legacy path entries otherwise. */
282
322
  paths: PathEntry[];
283
- /** Declared aggregate hash from the `["x","<hex>","aggregate"]` tag. */
323
+ /** Compatibility name for the signed content identity (current artifact or legacy aggregate). */
284
324
  aggregateHash: string;
285
325
  /** Blossom server URL hints from `server` tags. */
286
326
  servers: string[];
287
- /** Short NAP capability names from `requires` tags. */
327
+ /** Required bare domains from current R tags or legacy requires tags. */
288
328
  requires: string[];
289
329
  /**
290
- * Convention contracts this napplet fulfills, from `archetype` manifest tags.
330
+ * Routing projection: independent current z/i sets, or exact legacy archetype pairs.
291
331
  */
292
332
  archetypes: Array<{
293
333
  slug: string;
294
334
  convention: string;
335
+ params: string[];
295
336
  }>;
296
337
  /** Optional human title. */
297
338
  title?: string;
298
- /** Optional human description. */
339
+ /** Plain-text event content (legacy description tag for older events). */
299
340
  description?: string;
300
341
  /** Optional upstream source URL from the `source` tag. */
301
342
  source?: string;
302
343
  }
303
- /** Error codes for every napplet resolution failure path. */
304
- type NappletResolutionErrorCode = 'invalid-signature' | 'invalid-manifest' | 'aggregate-mismatch' | 'blob-hash-mismatch' | 'blob-unavailable' | 'missing-index';
305
- /**
306
- * Thrown on any napplet resolution/verification failure. The `code` field
307
- * identifies which guard rejected, so callers can fail closed without parsing
308
- * the message.
309
- */
310
- declare class NappletResolutionError extends Error {
311
- readonly code: NappletResolutionErrorCode;
312
- constructor(code: NappletResolutionErrorCode, message: string);
313
- }
344
+
314
345
  /**
315
- * Parse a NIP-5D manifest event into a {@link NappletManifest}.
316
- *
317
- * Does not verify the signature, aggregate, or blobs — use {@link resolveNapplet}
318
- * for full verification.
319
- *
320
- * @param event - A NIP-5D manifest event (`5129` / `15129` / `35129`)
321
- * @returns The parsed manifest
322
- * @throws {@link NappletResolutionError} `invalid-manifest` for a non-napplet
323
- * kind, missing `path` tags, or a missing aggregate `x` tag
346
+ * Normalize a current or legacy NIP-5D event without verifying its signature.
347
+ * @param event - Candidate manifest; use resolveNapplet before trusting it.
348
+ * @returns Manifest with normalized identity, paths, and routing declarations.
349
+ * @example
350
+ * const manifest = parseNappletManifest(event);
324
351
  */
325
352
  declare function parseNappletManifest(event: NostrEvent): NappletManifest;
326
353
  /**
@@ -353,9 +380,11 @@ declare function verifyBlobHash(bytes: Uint8Array, sha256Hex: string): boolean;
353
380
  declare function fetchBlob(servers: readonly string[], sha256Hex: string, fetchBytes: (url: string) => Promise<Uint8Array>): Promise<Uint8Array>;
354
381
  /** A fully verified napplet, ready to inject via `iframe.srcdoc`. */
355
382
  interface ResolvedNapplet {
383
+ /** Verified content identity; current artifact SHA-256 or legacy aggregate. */
384
+ artifactHash: string;
356
385
  /** Computed `d` identifier (`''` for root/snapshot). */
357
386
  dTag: string;
358
- /** Computed (and verified) aggregate hash — the content address. */
387
+ /** Compatibility alias for artifactHash; preserves existing host and ACL APIs. */
359
388
  aggregateHash: string;
360
389
  /** Verified file bytes keyed by manifest path. */
361
390
  files: Map<string, Uint8Array>;
@@ -379,14 +408,14 @@ interface ResolveNappletOptions {
379
408
  /**
380
409
  * Optional verified artifact cache. Cache hits are still re-verified against
381
410
  * the manifest hash before use; cache writes happen only after the signature,
382
- * aggregate, and every blob hash have been verified.
411
+ * content identity, and every blob hash have been verified.
383
412
  */
384
413
  cache?: NappletArtifactCache;
385
414
  }
386
415
  /**
387
416
  * Resolve a napplet end-to-end from a candidate manifest event: verify the
388
- * signature, parse the manifest, verify the NIP-5A aggregate, fetch and verify
389
- * every blob, then assemble the verified `/index.html`.
417
+ * signature, parse its schema, fetch and hash its artifact, and return verified
418
+ * `/index.html`. Legacy events additionally verify their NIP-5A aggregate.
390
419
  *
391
420
  * The returned `(dTag, aggregateHash)` is computed from the verified bytes and
392
421
  * is the napplet's identity. Any failure throws a {@link NappletResolutionError}
@@ -406,4 +435,4 @@ interface ResolveNappletOptions {
406
435
  */
407
436
  declare function resolveNapplet(options: ResolveNappletOptions): Promise<ResolvedNapplet>;
408
437
 
409
- export { CacheStorageNappletArtifactCache, type CacheStorageNappletArtifactCacheOptions, type CachedCoordinate, type CoordinateFreshnessOptions, NAPPLET_ARTIFACT_CACHE_NAME, NAPPLET_KINDS, NAPPLET_KIND_NAMED, NAPPLET_KIND_ROOT, NAPPLET_KIND_SNAPSHOT, type NappletAggregateIndexEntry, type NappletArtifactCache, type NappletArtifactCacheIndex, type NappletBlobIndexEntry, type NappletCacheDiagnostic, type NappletManifest, NappletResolutionError, type NappletResolutionErrorCode, type OpenNappletArtifactCacheOptions, type ResolveNappletOptions, type ResolvedNapplet, type WriteVerifiedResolutionInput, coordinateKey, fetchBlob, isCoordinateFresh, isNappletManifestKind, openNappletArtifactCache, parseNappletManifest, resolveNapplet, verifyBlobHash, verifyManifestSignature };
438
+ export { CacheStorageNappletArtifactCache, type CacheStorageNappletArtifactCacheOptions, type CachedCoordinate, type CoordinateFreshnessOptions, NAPPLET_ARTIFACT_CACHE_NAME, NAPPLET_KINDS, NAPPLET_KIND_NAMED, NAPPLET_KIND_ROOT, NAPPLET_KIND_SNAPSHOT, type NappletAggregateIndexEntry, type NappletArtifactCache, type NappletArtifactCacheIndex, type NappletBlobIndexEntry, type NappletCacheDiagnostic, type NappletManifest, NappletResolutionError, type NappletResolutionErrorCode, type OpenNappletArtifactCacheOptions, type ResolveNappletOptions, type ResolvedNapplet, type WriteVerifiedResolutionInput, coordinateKey, fetchBlob, getNappletCatalogId, isCoordinateFresh, isNappletManifestKind, openNappletArtifactCache, parseNappletManifest, resolveNapplet, verifyBlobHash, verifyManifestSignature };
package/dist/5d/index.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  NappletResolutionError,
9
9
  coordinateKey,
10
10
  fetchBlob,
11
+ getNappletCatalogId,
11
12
  isCoordinateFresh,
12
13
  isNappletManifestKind,
13
14
  openNappletArtifactCache,
@@ -15,7 +16,7 @@ import {
15
16
  resolveNapplet,
16
17
  verifyBlobHash,
17
18
  verifyManifestSignature
18
- } from "../chunk-F6ABV5KZ.js";
19
+ } from "../chunk-AKQIPGRC.js";
19
20
  import "../chunk-JT5WCCCX.js";
20
21
  export {
21
22
  CacheStorageNappletArtifactCache,
@@ -27,6 +28,7 @@ export {
27
28
  NappletResolutionError,
28
29
  coordinateKey,
29
30
  fetchBlob,
31
+ getNappletCatalogId,
30
32
  isCoordinateFresh,
31
33
  isNappletManifestKind,
32
34
  openNappletArtifactCache,
@@ -9,6 +9,232 @@ import { verifyEvent } from "nostr-tools/pure";
9
9
  import { sha256 } from "@noble/hashes/sha2.js";
10
10
  import { bytesToHex } from "@noble/hashes/utils.js";
11
11
 
12
+ // src/5d/errors.ts
13
+ var NappletResolutionError = class extends Error {
14
+ code;
15
+ constructor(code, message) {
16
+ super(message);
17
+ this.name = "NappletResolutionError";
18
+ this.code = code;
19
+ }
20
+ };
21
+
22
+ // src/5d/kinds.ts
23
+ var NAPPLET_KIND_SNAPSHOT = 5129;
24
+ var NAPPLET_KIND_ROOT = 15129;
25
+ var NAPPLET_KIND_NAMED = 35129;
26
+ var NAPPLET_KINDS = [
27
+ NAPPLET_KIND_SNAPSHOT,
28
+ NAPPLET_KIND_ROOT,
29
+ NAPPLET_KIND_NAMED
30
+ ];
31
+
32
+ // src/5d/catalog-id.ts
33
+ var PUBKEY = /^[a-f0-9]{64}$/;
34
+ function getNappletCatalogId(event) {
35
+ if (!PUBKEY.test(event.pubkey)) throw new TypeError("napplet manifest pubkey is invalid");
36
+ if (event.kind === NAPPLET_KIND_SNAPSHOT) {
37
+ if (!/^[a-f0-9]{64}$/.test(event.id)) throw new TypeError("napplet snapshot id is invalid");
38
+ return `nip5d:${NAPPLET_KIND_SNAPSHOT}:${event.id}`;
39
+ }
40
+ if (event.kind === NAPPLET_KIND_ROOT) return `nip5d:${NAPPLET_KIND_ROOT}:${event.pubkey}`;
41
+ if (event.kind === NAPPLET_KIND_NAMED) {
42
+ const dTags = event.tags.filter((tag) => tag[0] === "d");
43
+ if (dTags.length !== 1 || dTags[0].length !== 2 || !dTags[0][1]) {
44
+ throw new TypeError("named napplet manifest must have one non-empty d tag");
45
+ }
46
+ return `nip5d:${NAPPLET_KIND_NAMED}:${event.pubkey}:${dTags[0][1]}`;
47
+ }
48
+ throw new TypeError(`unsupported napplet manifest kind: ${event.kind}`);
49
+ }
50
+
51
+ // src/5d/legacy-manifest.ts
52
+ function firstTagValue(tags, name) {
53
+ for (const tag of tags) {
54
+ if (tag[0] === name && typeof tag[1] === "string" && tag[1].length > 0) return tag[1];
55
+ }
56
+ return void 0;
57
+ }
58
+ function allTagValues(tags, name) {
59
+ const out = [];
60
+ for (const tag of tags) {
61
+ if (tag[0] === name && typeof tag[1] === "string" && tag[1].length > 0) out.push(tag[1]);
62
+ }
63
+ return out;
64
+ }
65
+ function archetypesFromTags(tags) {
66
+ const out = [];
67
+ for (const tag of tags) {
68
+ if (tag[0] !== "archetype") continue;
69
+ const slug = tag[1];
70
+ if (typeof slug !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
71
+ throw new NappletResolutionError(
72
+ "invalid-manifest",
73
+ "archetype slug must contain lowercase letters, numbers, and hyphens"
74
+ );
75
+ }
76
+ const convention = tag[2];
77
+ if (typeof convention !== "string" || convention.length === 0) {
78
+ throw new NappletResolutionError("invalid-manifest", "archetype convention is required");
79
+ }
80
+ if (/^NAP-\d+$/.test(convention)) {
81
+ throw new NappletResolutionError(
82
+ "invalid-manifest",
83
+ "numbered NAP identifier is not an archetype convention"
84
+ );
85
+ }
86
+ if (!/^napplet:[^/?#\s]+\/[^/?#\s]+$/.test(convention)) {
87
+ throw new NappletResolutionError(
88
+ "invalid-manifest",
89
+ "archetype convention must be a queryless napplet:<archetype>/<intent> identity"
90
+ );
91
+ }
92
+ if (tag.length !== 3) {
93
+ throw new NappletResolutionError(
94
+ "invalid-manifest",
95
+ "archetype tags must contain exactly slug and convention"
96
+ );
97
+ }
98
+ out.push({ slug, convention, params: [] });
99
+ }
100
+ return out;
101
+ }
102
+ function parseLegacyManifest(event) {
103
+ const paths = pathEntriesFromTags(event.tags);
104
+ if (paths.length === 0) {
105
+ throw new NappletResolutionError("invalid-manifest", "manifest has no path tags");
106
+ }
107
+ const aggregateHash = aggregateTagValue(event.tags);
108
+ if (!aggregateHash) {
109
+ throw new NappletResolutionError("invalid-manifest", "manifest has no aggregate x tag");
110
+ }
111
+ const dTag = firstTagValue(event.tags, "d");
112
+ if (event.kind !== 35129 && dTag !== void 0) {
113
+ throw new NappletResolutionError("invalid-manifest", "only named manifests may carry a d tag");
114
+ }
115
+ return {
116
+ format: "legacy",
117
+ artifactHash: aggregateHash,
118
+ optional: [],
119
+ kind: event.kind,
120
+ pubkey: event.pubkey,
121
+ dTag: dTag ?? "",
122
+ catalogId: getNappletCatalogId(event),
123
+ paths,
124
+ aggregateHash,
125
+ servers: allTagValues(event.tags, "server"),
126
+ requires: allTagValues(event.tags, "requires"),
127
+ archetypes: archetypesFromTags(event.tags),
128
+ title: firstTagValue(event.tags, "title"),
129
+ description: firstTagValue(event.tags, "description"),
130
+ source: firstTagValue(event.tags, "source")
131
+ };
132
+ }
133
+ function verifyLegacyAggregate(manifest) {
134
+ const recomputed = computeAggregateHash(manifest.paths);
135
+ if (recomputed !== manifest.aggregateHash) {
136
+ throw new NappletResolutionError(
137
+ "aggregate-mismatch",
138
+ `recomputed aggregate ${recomputed} != manifest ${manifest.aggregateHash}`
139
+ );
140
+ }
141
+ }
142
+ var warnedLegacyManifest = false;
143
+ function warnLegacyManifest() {
144
+ if (warnedLegacyManifest) return;
145
+ warnedLegacyManifest = true;
146
+ console.warn("[KEHTO_COMPAT_LEGACY_MANIFEST] Legacy aggregate manifest resolved. Republish with @napplet/vite-plugin >=0.15 using the current single-artifact schema. See docs/compatibility.md.");
147
+ }
148
+
149
+ // src/5d/current-manifest.ts
150
+ var HASH = /^[a-f0-9]{64}$/;
151
+ var DOMAIN = /^[a-z][a-z0-9-]*$/;
152
+ var SLUG = /^[a-z0-9][a-z0-9-]*$/;
153
+ function invalid(message) {
154
+ throw new NappletResolutionError("invalid-manifest", message);
155
+ }
156
+ function single(tags, name) {
157
+ const matches = tags.filter((tag) => tag[0] === name);
158
+ if (matches.length > 1 || matches.some((tag) => tag.length !== 2 || !tag[1])) {
159
+ invalid(`manifest ${name} tag must have one value and occur at most once`);
160
+ }
161
+ return matches[0]?.[1];
162
+ }
163
+ function declarations(tags, name, pattern) {
164
+ return [...new Set(tags.filter((tag) => tag[0] === name).map((tag) => {
165
+ if (tag.length !== 2 || !pattern.test(tag[1])) invalid(`invalid ${name} declaration`);
166
+ return tag[1];
167
+ }))];
168
+ }
169
+ function intentDeclarations(tags, roles) {
170
+ const roleSet = new Set(roles);
171
+ const contracts = [];
172
+ for (const tag of tags) {
173
+ if (tag[0] !== "i") continue;
174
+ const [, identity, ...params] = tag;
175
+ const match = typeof identity === "string" ? /^napplet:([a-z0-9][a-z0-9-]*)\/[^/?#\s]+$/.exec(identity) : null;
176
+ if (!match || params.some((param) => !param || /\s/.test(param))) continue;
177
+ if (!roleSet.has(match[1])) continue;
178
+ contracts.push({ slug: match[1], convention: identity, params });
179
+ }
180
+ return contracts;
181
+ }
182
+ function iconFromTags(tags) {
183
+ const icons = tags.filter((tag) => tag[0] === "icon");
184
+ if (icons.length !== 1) return void 0;
185
+ const [, hash, mimeType] = icons[0];
186
+ if (icons[0].length !== 3 || !HASH.test(hash)) return void 0;
187
+ if (mimeType !== "image/png" && mimeType !== "image/jpeg" && mimeType !== "image/webp") {
188
+ return void 0;
189
+ }
190
+ return { sha256: hash, mimeType };
191
+ }
192
+ function parseCurrentManifest(event) {
193
+ const tags = event.tags;
194
+ const artifactHash = single(tags, "x");
195
+ if (!artifactHash || !HASH.test(artifactHash)) invalid("invalid artifact x hash");
196
+ if (typeof event.content !== "string" || !event.content.trim()) {
197
+ invalid("manifest content must be a non-empty plain-text description");
198
+ }
199
+ const dTag = single(tags, "d");
200
+ if (event.kind === 35129 ? dTag === void 0 : dTag !== void 0) {
201
+ invalid("exactly one d tag is required only on named manifests");
202
+ }
203
+ const parent = single(tags, "a");
204
+ const root = single(tags, "A");
205
+ for (const address of [parent, root]) {
206
+ if (address === void 0) continue;
207
+ if (event.kind !== 5129 || !/^(35129:[a-f0-9]{64}:.+|15129:[a-f0-9]{64}:)$/.test(address)) {
208
+ invalid("lineage must be a napplet address on a snapshot");
209
+ }
210
+ }
211
+ const archetypeSlugs = [...new Set(tags.filter((tag) => tag[0] === "z" && tag.length === 2 && SLUG.test(tag[1])).map((tag) => tag[1]))];
212
+ const archetypes = intentDeclarations(tags, archetypeSlugs);
213
+ const intents = archetypes.map(({ convention: identity, params: parameters }) => ({ identity, parameters }));
214
+ return {
215
+ format: "current",
216
+ kind: event.kind,
217
+ pubkey: event.pubkey,
218
+ dTag: dTag ?? "",
219
+ catalogId: getNappletCatalogId(event),
220
+ artifactHash,
221
+ aggregateHash: artifactHash,
222
+ paths: [{ path: "/index.html", sha256: artifactHash }],
223
+ servers: tags.filter((tag) => tag[0] === "server" && tag[1]).map((tag) => tag[1]),
224
+ requires: declarations(tags, "R", DOMAIN),
225
+ optional: declarations(tags, "O", DOMAIN),
226
+ archetypeSlugs,
227
+ intents,
228
+ archetypes,
229
+ title: single(tags, "title"),
230
+ description: event.content,
231
+ source: single(tags, "source"),
232
+ icon: iconFromTags(tags),
233
+ parent,
234
+ root
235
+ };
236
+ }
237
+
12
238
  // src/5d/artifact-cache.ts
13
239
  var MIB = 1024 * 1024;
14
240
  var NAPPLET_ARTIFACT_CACHE_NAME = "kehto:napplet-artifacts:v1";
@@ -410,104 +636,18 @@ async function openNappletArtifactCache(options = {}) {
410
636
  }
411
637
 
412
638
  // src/5d/index.ts
413
- var NAPPLET_KIND_SNAPSHOT = 5129;
414
- var NAPPLET_KIND_ROOT = 15129;
415
- var NAPPLET_KIND_NAMED = 35129;
416
- var NAPPLET_KINDS = [
417
- NAPPLET_KIND_SNAPSHOT,
418
- NAPPLET_KIND_ROOT,
419
- NAPPLET_KIND_NAMED
420
- ];
421
639
  function isNappletManifestKind(kind) {
422
640
  return NAPPLET_KINDS.includes(kind);
423
641
  }
424
- var NappletResolutionError = class extends Error {
425
- code;
426
- constructor(code, message) {
427
- super(message);
428
- this.name = "NappletResolutionError";
429
- this.code = code;
430
- }
431
- };
432
- function firstTagValue(tags, name) {
433
- for (const tag of tags) {
434
- if (tag[0] === name && typeof tag[1] === "string" && tag[1].length > 0) return tag[1];
435
- }
436
- return void 0;
437
- }
438
- function allTagValues(tags, name) {
439
- const out = [];
440
- for (const tag of tags) {
441
- if (tag[0] === name && typeof tag[1] === "string" && tag[1].length > 0) out.push(tag[1]);
442
- }
443
- return out;
444
- }
445
- function archetypesFromTags(tags) {
446
- const out = [];
447
- for (const tag of tags) {
448
- if (tag[0] !== "archetype") continue;
449
- const slug = tag[1];
450
- if (typeof slug !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {
451
- throw new NappletResolutionError(
452
- "invalid-manifest",
453
- "archetype slug must contain lowercase letters, numbers, and hyphens"
454
- );
455
- }
456
- const convention = tag[2];
457
- if (typeof convention !== "string" || convention.length === 0) {
458
- throw new NappletResolutionError("invalid-manifest", "archetype convention is required");
459
- }
460
- if (/^NAP-\d+$/.test(convention)) {
461
- throw new NappletResolutionError(
462
- "invalid-manifest",
463
- "numbered NAP identifier is not an archetype convention"
464
- );
465
- }
466
- if (!/^napplet:[^/?#\s]+\/[^/?#\s]+$/.test(convention)) {
467
- throw new NappletResolutionError(
468
- "invalid-manifest",
469
- "archetype convention must be a queryless napplet:<archetype>/<intent> identity"
470
- );
471
- }
472
- if (tag.length !== 3) {
473
- throw new NappletResolutionError(
474
- "invalid-manifest",
475
- "archetype tags must contain exactly slug and convention"
476
- );
477
- }
478
- out.push({ slug, convention });
479
- }
480
- return out;
481
- }
482
642
  function parseNappletManifest(event) {
483
643
  if (!isNappletManifestKind(event.kind)) {
484
644
  throw new NappletResolutionError("invalid-manifest", `not a NIP-5D napplet kind: ${event.kind}`);
485
645
  }
486
- const paths = pathEntriesFromTags(event.tags);
487
- if (paths.length === 0) {
488
- throw new NappletResolutionError("invalid-manifest", "manifest has no path tags");
489
- }
490
- const aggregateHash = aggregateTagValue(event.tags);
491
- if (!aggregateHash) {
492
- throw new NappletResolutionError("invalid-manifest", "manifest has no aggregate x tag");
493
- }
494
- const dTag = firstTagValue(event.tags, "d");
495
- if (event.kind !== NAPPLET_KIND_NAMED && dTag !== void 0) {
496
- throw new NappletResolutionError("invalid-manifest", "only named manifests may carry a d tag");
646
+ const hashes = event.tags.filter((tag) => tag[0] === "x");
647
+ if (hashes.length !== 1) {
648
+ throw new NappletResolutionError("invalid-manifest", "manifest must carry exactly one x tag");
497
649
  }
498
- return {
499
- kind: event.kind,
500
- pubkey: event.pubkey,
501
- dTag: dTag ?? "",
502
- paths,
503
- aggregateHash,
504
- servers: allTagValues(event.tags, "server"),
505
- requires: allTagValues(event.tags, "requires"),
506
- archetypes: archetypesFromTags(event.tags),
507
- title: firstTagValue(event.tags, "title"),
508
- description: firstTagValue(event.tags, "description"),
509
- source: firstTagValue(event.tags, "source")
510
- };
650
+ return hashes[0][2] === "aggregate" ? parseLegacyManifest(event) : parseCurrentManifest(event);
511
651
  }
512
652
  function verifyManifestSignature(event) {
513
653
  try {
@@ -543,13 +683,7 @@ async function resolveNapplet(options) {
543
683
  throw new NappletResolutionError("invalid-signature", "manifest signature is invalid");
544
684
  }
545
685
  const manifest = parseNappletManifest(event);
546
- const recomputed = computeAggregateHash(manifest.paths);
547
- if (recomputed !== manifest.aggregateHash) {
548
- throw new NappletResolutionError(
549
- "aggregate-mismatch",
550
- `recomputed aggregate ${recomputed} != manifest ${manifest.aggregateHash}`
551
- );
552
- }
686
+ if (manifest.format === "legacy") verifyLegacyAggregate(manifest);
553
687
  const files = /* @__PURE__ */ new Map();
554
688
  for (const entry of manifest.paths) {
555
689
  let bytes = await cache?.readBlob(entry.sha256);
@@ -572,7 +706,9 @@ async function resolveNapplet(options) {
572
706
  }
573
707
  const indexHtml = textDecode(files.get(indexEntry.path));
574
708
  await cache?.writeVerifiedResolution({ event, manifest, files, indexHtml });
709
+ if (manifest.format === "legacy") warnLegacyManifest();
575
710
  return {
711
+ artifactHash: manifest.aggregateHash,
576
712
  dTag: manifest.dTag,
577
713
  aggregateHash: manifest.aggregateHash,
578
714
  files,
@@ -582,21 +718,22 @@ async function resolveNapplet(options) {
582
718
  }
583
719
 
584
720
  export {
721
+ NappletResolutionError,
722
+ NAPPLET_KIND_SNAPSHOT,
723
+ NAPPLET_KIND_ROOT,
724
+ NAPPLET_KIND_NAMED,
725
+ NAPPLET_KINDS,
726
+ getNappletCatalogId,
585
727
  NAPPLET_ARTIFACT_CACHE_NAME,
586
728
  coordinateKey,
587
729
  isCoordinateFresh,
588
730
  CacheStorageNappletArtifactCache,
589
731
  openNappletArtifactCache,
590
- NAPPLET_KIND_SNAPSHOT,
591
- NAPPLET_KIND_ROOT,
592
- NAPPLET_KIND_NAMED,
593
- NAPPLET_KINDS,
594
732
  isNappletManifestKind,
595
- NappletResolutionError,
596
733
  parseNappletManifest,
597
734
  verifyManifestSignature,
598
735
  verifyBlobHash,
599
736
  fetchBlob,
600
737
  resolveNapplet
601
738
  };
602
- //# sourceMappingURL=chunk-F6ABV5KZ.js.map
739
+ //# sourceMappingURL=chunk-AKQIPGRC.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/5d/index.ts","../src/5d/errors.ts","../src/5d/kinds.ts","../src/5d/catalog-id.ts","../src/5d/legacy-manifest.ts","../src/5d/current-manifest.ts","../src/5d/artifact-cache.ts"],"sourcesContent":["import type { NostrEvent } from 'nostr-tools';\nimport { verifyEvent } from 'nostr-tools/pure';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\nimport type { PathEntry } from '../5a/index.js';\nimport { NappletResolutionError } from './errors.js';\nimport { parseLegacyManifest, verifyLegacyAggregate, warnLegacyManifest } from './legacy-manifest.js';\nimport { parseCurrentManifest } from './current-manifest.js';\nimport {\n NAPPLET_KIND_NAMED,\n NAPPLET_KIND_ROOT,\n NAPPLET_KIND_SNAPSHOT,\n NAPPLET_KINDS,\n} from './kinds.js';\nimport type { NappletArtifactCache } from './artifact-cache.js';\nexport {\n CacheStorageNappletArtifactCache,\n NAPPLET_ARTIFACT_CACHE_NAME,\n coordinateKey,\n isCoordinateFresh,\n openNappletArtifactCache,\n type CachedCoordinate,\n type CacheStorageNappletArtifactCacheOptions,\n type CoordinateFreshnessOptions,\n type NappletAggregateIndexEntry,\n type NappletArtifactCacheIndex,\n type NappletArtifactCache,\n type NappletBlobIndexEntry,\n type NappletCacheDiagnostic,\n type OpenNappletArtifactCacheOptions,\n type WriteVerifiedResolutionInput,\n} from './artifact-cache.js';\n\n/**\n * `@kehto/nip/5d` — verify signed napplet manifests and their content bytes.\n * Current events use one artifact x hash. The isolated legacy adapter accepts\n * path tags plus an aggregate x marker. Both formats share signature checking,\n * blob verification, caching, and verified srcdoc output.\n * @module\n */\n\n/** Snapshot manifest — regular event, immutable point-in-time release. */\nexport { NAPPLET_KIND_SNAPSHOT, NAPPLET_KIND_ROOT, NAPPLET_KIND_NAMED, NAPPLET_KINDS };\n/** Root manifest — replaceable event, an author's latest unnamed napplet. */\n/** Named manifest — addressable event (carries a `d` tag identifier). */\n\n/** All three NIP-5D napplet manifest kinds. */\nexport { getNappletCatalogId } from './catalog-id.js';\n\n/**\n * Whether `kind` is one of the three NIP-5D napplet manifest kinds.\n *\n * @param kind - A Nostr event kind\n * @returns `true` for `5129` / `15129` / `35129`\n */\nexport function isNappletManifestKind(kind: number): boolean {\n return NAPPLET_KINDS.includes(kind);\n}\n\n/**\n * A parsed NIP-5D napplet manifest. All fields are derived from the manifest\n * event. Declarations are untrusted until {@link resolveNapplet} verifies both\n * the signature and the content bytes.\n */\nexport interface NappletManifest {\n /** Event format selected by the parser. Absent only in older host-created values. */\n format?: 'current' | 'legacy';\n /** Signed artifact hash for current events; legacy aggregate for older events. */\n artifactHash?: string;\n /** Optional domains; absence never prevents loading and presence grants nothing. */\n optional?: string[];\n /** Independent current z declarations, preserved even without accepted intents. */\n archetypeSlugs?: string[];\n /** Accepted queryless intents and advertised parameter names from i tags. */\n intents?: Array<{ identity: string; parameters: string[] }>;\n /** Valid supported icon declaration. Hosts may keep generic artwork. */\n icon?: { sha256: string; mimeType: 'image/png' | 'image/jpeg' | 'image/webp' };\n /** Snapshot provenance only; never resolved or trusted on its behalf. */\n parent?: string;\n /** Snapshot root provenance only. */\n root?: string;\n /** Manifest event kind (`5129` / `15129` / `35129`). */\n kind: number;\n /** Author hex public key. */\n pubkey: string;\n /** Named-napplet `d` identifier, or `''` for root/snapshot manifests. */\n dTag: string;\n /** Publisher/kind-safe opaque identity for the verified manifest catalog. */\n catalogId: string;\n /** Normalized files: one /index.html for current events, legacy path entries otherwise. */\n paths: PathEntry[];\n /** Compatibility name for the signed content identity (current artifact or legacy aggregate). */\n aggregateHash: string;\n /** Blossom server URL hints from `server` tags. */\n servers: string[];\n /** Required bare domains from current R tags or legacy requires tags. */\n requires: string[];\n /**\n * Routing projection: independent current z/i sets, or exact legacy archetype pairs.\n */\n archetypes: Array<{ slug: string; convention: string; params: string[] }>;\n /** Optional human title. */\n title?: string;\n /** Plain-text event content (legacy description tag for older events). */\n description?: string;\n /** Optional upstream source URL from the `source` tag. */\n source?: string;\n}\n\nexport { NappletResolutionError, type NappletResolutionErrorCode } from './errors.js';\n\n/**\n * Normalize a current or legacy NIP-5D event without verifying its signature.\n * @param event - Candidate manifest; use resolveNapplet before trusting it.\n * @returns Manifest with normalized identity, paths, and routing declarations.\n * @example\n * const manifest = parseNappletManifest(event);\n */\nexport function parseNappletManifest(event: NostrEvent): NappletManifest {\n if (!isNappletManifestKind(event.kind)) {\n throw new NappletResolutionError('invalid-manifest', `not a NIP-5D napplet kind: ${event.kind}`);\n }\n const hashes = event.tags.filter((tag) => tag[0] === 'x');\n if (hashes.length !== 1) {\n throw new NappletResolutionError('invalid-manifest', 'manifest must carry exactly one x tag');\n }\n // Explicit format selection, never retry a malformed current event as legacy.\n return hashes[0][2] === 'aggregate'\n ? parseLegacyManifest(event)\n : parseCurrentManifest(event);\n}\n\n/**\n * Verify a manifest event's Nostr signature (id + schnorr sig).\n *\n * @param event - A manifest event\n * @returns `true` if the event is internally consistent and validly signed\n */\nexport function verifyManifestSignature(event: NostrEvent): boolean {\n try {\n return verifyEvent(event);\n } catch {\n return false;\n }\n}\n\n/**\n * Whether `bytes` hash to the expected lowercase-hex SHA-256.\n *\n * @param bytes - The blob bytes\n * @param sha256Hex - Expected lowercase-hex SHA-256\n * @returns `true` only on an exact match\n */\nexport function verifyBlobHash(bytes: Uint8Array, sha256Hex: string): boolean {\n return bytesToHex(sha256(bytes)) === sha256Hex;\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.endsWith('/') ? url.slice(0, -1) : url;\n}\n\n/**\n * Fetch a blob by SHA-256 from a list of Blossom servers, returning the first\n * server's bytes whose hash matches. The returned bytes are always re-verified\n * against `sha256Hex` — servers (and gateways) are never trusted.\n *\n * @param servers - Candidate Blossom server base URLs, tried in order\n * @param sha256Hex - The blob's lowercase-hex SHA-256\n * @param fetchBytes - Fetches raw bytes for a URL (`<server>/<sha256>`)\n * @returns The verified blob bytes\n * @throws {@link NappletResolutionError} `blob-unavailable` if no server serves\n * a hash-matching blob\n */\nexport async function fetchBlob(\n servers: readonly string[],\n sha256Hex: string,\n fetchBytes: (url: string) => Promise<Uint8Array>,\n): Promise<Uint8Array> {\n for (const server of servers) {\n const url = `${stripTrailingSlash(server)}/${sha256Hex}`;\n try {\n const bytes = await fetchBytes(url);\n if (verifyBlobHash(bytes, sha256Hex)) return bytes;\n } catch {\n // try the next server\n }\n }\n throw new NappletResolutionError('blob-unavailable', `no server served blob ${sha256Hex}`);\n}\n\n/** A fully verified napplet, ready to inject via `iframe.srcdoc`. */\nexport interface ResolvedNapplet {\n /** Verified content identity; current artifact SHA-256 or legacy aggregate. */\n artifactHash: string;\n /** Computed `d` identifier (`''` for root/snapshot). */\n dTag: string;\n /** Compatibility alias for artifactHash; preserves existing host and ACL APIs. */\n aggregateHash: string;\n /** Verified file bytes keyed by manifest path. */\n files: Map<string, Uint8Array>;\n /** The verified `/index.html` decoded to text. */\n indexHtml: string;\n /** The parsed manifest. */\n manifest: NappletManifest;\n}\n\n/** Options for {@link resolveNapplet}. */\nexport interface ResolveNappletOptions {\n /** The candidate manifest event (resolved from relays by the caller). */\n event: NostrEvent;\n /**\n * Fetch raw blob bytes by SHA-256. Backed by Blossom (or a gateway) by the\n * caller. The bytes are re-verified against the hash here, so the fetcher is\n * untrusted.\n */\n fetchBlob: (sha256Hex: string, servers: readonly string[]) => Promise<Uint8Array>;\n /** Decode blob bytes to text for `indexHtml` (default UTF-8). */\n textDecode?: (bytes: Uint8Array) => string;\n /**\n * Optional verified artifact cache. Cache hits are still re-verified against\n * the manifest hash before use; cache writes happen only after the signature,\n * content identity, and every blob hash have been verified.\n */\n cache?: NappletArtifactCache;\n}\n\nconst INDEX_PATHS = ['/index.html', 'index.html', '/'];\n\nfunction defaultDecode(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Resolve a napplet end-to-end from a candidate manifest event: verify the\n * signature, parse its schema, fetch and hash its artifact, and return verified\n * `/index.html`. Legacy events additionally verify their NIP-5A aggregate.\n *\n * The returned `(dTag, aggregateHash)` is computed from the verified bytes and\n * is the napplet's identity. Any failure throws a {@link NappletResolutionError}\n * — the caller must fail closed (never render unverified bytes).\n *\n * @param options - {@link ResolveNappletOptions}\n * @returns The {@link ResolvedNapplet}\n * @throws {@link NappletResolutionError} on signature, manifest, aggregate,\n * blob-hash, blob-availability, or missing-index failures\n *\n * @example\n * ```ts\n * import { resolveNapplet } from '@kehto/nip/5d';\n * const napplet = await resolveNapplet({ event, fetchBlob });\n * iframe.srcdoc = napplet.indexHtml; // sandbox=\"allow-scripts\", opaque origin\n * ```\n */\nexport async function resolveNapplet(options: ResolveNappletOptions): Promise<ResolvedNapplet> {\n const { event, fetchBlob: fetchBlobBytes, textDecode = defaultDecode, cache } = options;\n\n if (!verifyManifestSignature(event)) {\n throw new NappletResolutionError('invalid-signature', 'manifest signature is invalid');\n }\n\n const manifest = parseNappletManifest(event);\n\n if (manifest.format === 'legacy') verifyLegacyAggregate(manifest);\n\n const files = new Map<string, Uint8Array>();\n for (const entry of manifest.paths) {\n let bytes = await cache?.readBlob(entry.sha256);\n if (bytes && !verifyBlobHash(bytes, entry.sha256)) {\n await cache?.deleteBlob(entry.sha256);\n bytes = undefined;\n }\n bytes ??= await fetchBlobBytes(entry.sha256, manifest.servers);\n if (!verifyBlobHash(bytes, entry.sha256)) {\n throw new NappletResolutionError(\n 'blob-hash-mismatch',\n `blob for ${entry.path} does not match hash ${entry.sha256}`,\n );\n }\n files.set(entry.path, bytes);\n }\n\n const indexEntry = manifest.paths.find((e) => INDEX_PATHS.includes(e.path));\n if (!indexEntry) {\n throw new NappletResolutionError('missing-index', 'manifest has no /index.html entry');\n }\n\n const indexHtml = textDecode(files.get(indexEntry.path)!);\n await cache?.writeVerifiedResolution({ event, manifest, files, indexHtml });\n if (manifest.format === 'legacy') warnLegacyManifest();\n\n return {\n artifactHash: manifest.aggregateHash,\n dTag: manifest.dTag,\n aggregateHash: manifest.aggregateHash,\n files,\n indexHtml,\n manifest,\n };\n}\n","/** Error codes for every napplet resolution failure path. */\nexport type NappletResolutionErrorCode =\n | 'invalid-signature'\n | 'invalid-manifest'\n | 'aggregate-mismatch'\n | 'blob-hash-mismatch'\n | 'blob-unavailable'\n | 'missing-index';\n\n/**\n * Thrown on any napplet resolution/verification failure. The `code` field\n * identifies which guard rejected, so callers can fail closed without parsing\n * the message.\n */\nexport class NappletResolutionError extends Error {\n readonly code: NappletResolutionErrorCode;\n constructor(code: NappletResolutionErrorCode, message: string) {\n super(message);\n this.name = 'NappletResolutionError';\n this.code = code;\n }\n}\n\n","/** NIP-5D napplet manifest event kinds. */\n\n/** Snapshot manifest — regular event, immutable point-in-time release. */\nexport const NAPPLET_KIND_SNAPSHOT = 5129;\n/** Root manifest — replaceable event, an author's latest unnamed napplet. */\nexport const NAPPLET_KIND_ROOT = 15129;\n/** Named manifest — addressable event (carries a `d` tag identifier). */\nexport const NAPPLET_KIND_NAMED = 35129;\n\n/** All three NIP-5D napplet manifest kinds. */\nexport const NAPPLET_KINDS: readonly number[] = [\n NAPPLET_KIND_SNAPSHOT,\n NAPPLET_KIND_ROOT,\n NAPPLET_KIND_NAMED,\n];\n","/** Publisher-safe catalog identities for verified NIP-5D manifest events. */\n\nimport type { NostrEvent } from 'nostr-tools';\nimport {\n NAPPLET_KIND_NAMED,\n NAPPLET_KIND_ROOT,\n NAPPLET_KIND_SNAPSHOT,\n} from './kinds.js';\n\nconst PUBKEY = /^[a-f0-9]{64}$/;\n\n/**\n * Derive the runtime catalog identity for a verified NIP-5D manifest event.\n *\n * Named and root manifests retain a stable publisher-scoped identity while a\n * snapshot is identified by its immutable signed event id. Call this only after\n * the event has passed signature and manifest verification.\n *\n * @param event - A verified NIP-5D manifest event.\n * @returns The opaque publisher/kind-safe catalog ID.\n *\n * @example\n * ```ts\n * getNappletCatalogId(namedManifest);\n * // → 'nip5d:35129:<publisher>:<literal-d-tag>'\n * ```\n */\nexport function getNappletCatalogId(event: Pick<NostrEvent, 'id' | 'kind' | 'pubkey' | 'tags'>): string {\n if (!PUBKEY.test(event.pubkey)) throw new TypeError('napplet manifest pubkey is invalid');\n if (event.kind === NAPPLET_KIND_SNAPSHOT) {\n if (!/^[a-f0-9]{64}$/.test(event.id)) throw new TypeError('napplet snapshot id is invalid');\n return `nip5d:${NAPPLET_KIND_SNAPSHOT}:${event.id}`;\n }\n if (event.kind === NAPPLET_KIND_ROOT) return `nip5d:${NAPPLET_KIND_ROOT}:${event.pubkey}`;\n if (event.kind === NAPPLET_KIND_NAMED) {\n const dTags = event.tags.filter((tag) => tag[0] === 'd');\n if (dTags.length !== 1 || dTags[0].length !== 2 || !dTags[0][1]) {\n throw new TypeError('named napplet manifest must have one non-empty d tag');\n }\n // `d` is opaque and case-sensitive: deliberately preserve it byte-for-byte.\n return `nip5d:${NAPPLET_KIND_NAMED}:${event.pubkey}:${dTags[0][1]}`;\n }\n throw new TypeError(`unsupported napplet manifest kind: ${event.kind}`);\n}\n","// Removal boundary for pre-0.15 @napplet/vite-plugin event compatibility.\nimport type { NostrEvent } from 'nostr-tools';\nimport { computeAggregateHash, pathEntriesFromTags, aggregateTagValue } from '../5a/index.js';\nimport type { NappletManifest } from './index.js';\nimport { NappletResolutionError } from './errors.js';\nimport { getNappletCatalogId } from './catalog-id.js';\n\nfunction firstTagValue(tags: readonly (readonly string[])[], name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name && typeof tag[1] === 'string' && tag[1].length > 0) return tag[1];\n }\n return undefined;\n}\n\nfunction allTagValues(tags: readonly (readonly string[])[], name: string): string[] {\n const out: string[] = [];\n for (const tag of tags) {\n if (tag[0] === name && typeof tag[1] === 'string' && tag[1].length > 0) out.push(tag[1]);\n }\n return out;\n}\n\nfunction archetypesFromTags(\n tags: readonly (readonly string[])[],\n): Array<{ slug: string; convention: string; params: string[] }> {\n const out: Array<{ slug: string; convention: string; params: string[] }> = [];\n for (const tag of tags) {\n if (tag[0] !== 'archetype') continue;\n const slug = tag[1];\n if (typeof slug !== 'string' || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {\n throw new NappletResolutionError(\n 'invalid-manifest',\n 'archetype slug must contain lowercase letters, numbers, and hyphens',\n );\n }\n\n const convention = tag[2];\n if (typeof convention !== 'string' || convention.length === 0) {\n throw new NappletResolutionError('invalid-manifest', 'archetype convention is required');\n }\n if (/^NAP-\\d+$/.test(convention)) {\n throw new NappletResolutionError(\n 'invalid-manifest',\n 'numbered NAP identifier is not an archetype convention',\n );\n }\n if (!/^napplet:[^/?#\\s]+\\/[^/?#\\s]+$/.test(convention)) {\n throw new NappletResolutionError(\n 'invalid-manifest',\n 'archetype convention must be a queryless napplet:<archetype>/<intent> identity',\n );\n }\n // NAP-INTENT keeps a routing archetype and a payload convention\n // orthogonal: one convention may serve multiple archetypes and vice versa.\n\n if (tag.length !== 3) {\n throw new NappletResolutionError(\n 'invalid-manifest',\n 'archetype tags must contain exactly slug and convention',\n );\n }\n out.push({ slug, convention, params: [] });\n }\n return out;\n}\n\nexport function parseLegacyManifest(event: NostrEvent): NappletManifest {\n const paths = pathEntriesFromTags(event.tags);\n if (paths.length === 0) {\n throw new NappletResolutionError('invalid-manifest', 'manifest has no path tags');\n }\n const aggregateHash = aggregateTagValue(event.tags);\n if (!aggregateHash) {\n throw new NappletResolutionError('invalid-manifest', 'manifest has no aggregate x tag');\n }\n const dTag = firstTagValue(event.tags, 'd');\n if (event.kind !== 35129 && dTag !== undefined) {\n throw new NappletResolutionError('invalid-manifest', 'only named manifests may carry a d tag');\n }\n return {\n format: 'legacy',\n artifactHash: aggregateHash,\n optional: [],\n kind: event.kind,\n pubkey: event.pubkey,\n dTag: dTag ?? '',\n catalogId: getNappletCatalogId(event),\n paths,\n aggregateHash,\n servers: allTagValues(event.tags, 'server'),\n requires: allTagValues(event.tags, 'requires'),\n archetypes: archetypesFromTags(event.tags),\n title: firstTagValue(event.tags, 'title'),\n description: firstTagValue(event.tags, 'description'),\n source: firstTagValue(event.tags, 'source'),\n };\n}\n\n\n/** Compatibility-only aggregate check; current manifests never enter this path. */\nexport function verifyLegacyAggregate(manifest: NappletManifest): void {\n const recomputed = computeAggregateHash(manifest.paths);\n if (recomputed !== manifest.aggregateHash) {\n throw new NappletResolutionError('aggregate-mismatch',\n `recomputed aggregate ${recomputed} != manifest ${manifest.aggregateHash}`);\n }\n}\n\nlet warnedLegacyManifest = false;\n\n/** Warn once per module lifetime, after a legacy artifact has passed verification. */\nexport function warnLegacyManifest(): void {\n if (warnedLegacyManifest) return;\n warnedLegacyManifest = true;\n console.warn('[KEHTO_COMPAT_LEGACY_MANIFEST] Legacy aggregate manifest resolved. Republish with @napplet/vite-plugin >=0.15 using the current single-artifact schema. See docs/compatibility.md.');\n}\n","import type { NostrEvent } from 'nostr-tools';\nimport type { NappletManifest } from './index.js';\nimport { NappletResolutionError } from './errors.js';\nimport { getNappletCatalogId } from './catalog-id.js';\n\nconst HASH = /^[a-f0-9]{64}$/;\nconst DOMAIN = /^[a-z][a-z0-9-]*$/;\nconst SLUG = /^[a-z0-9][a-z0-9-]*$/;\n\nfunction invalid(message: string): never {\n throw new NappletResolutionError('invalid-manifest', message);\n}\n\nfunction single(tags: string[][], name: string): string | undefined {\n const matches = tags.filter((tag) => tag[0] === name);\n if (matches.length > 1 || matches.some((tag) => tag.length !== 2 || !tag[1])) {\n invalid(`manifest ${name} tag must have one value and occur at most once`);\n }\n return matches[0]?.[1];\n}\n\nfunction declarations(tags: string[][], name: string, pattern: RegExp): string[] {\n return [...new Set(tags.filter((tag) => tag[0] === name).map((tag) => {\n if (tag.length !== 2 || !pattern.test(tag[1])) invalid(`invalid ${name} declaration`);\n return tag[1];\n }))];\n}\n\nfunction intentDeclarations(tags: string[][], roles: readonly string[]): Array<{\n slug: string;\n convention: string;\n params: string[];\n}> {\n const roleSet = new Set(roles);\n const contracts: Array<{ slug: string; convention: string; params: string[] }> = [];\n for (const tag of tags) {\n if (tag[0] !== 'i') continue;\n const [, identity, ...params] = tag;\n const match = typeof identity === 'string' ? /^napplet:([a-z0-9][a-z0-9-]*)\\/[^/?#\\s]+$/.exec(identity) : null;\n if (!match || params.some((param) => !param || /\\s/.test(param))) continue;\n // A current `i` advertisement is eligible only for the same declared role.\n if (!roleSet.has(match[1])) continue;\n contracts.push({ slug: match[1], convention: identity, params });\n }\n return contracts;\n}\n\nfunction iconFromTags(tags: string[][]): NappletManifest['icon'] {\n const icons = tags.filter((tag) => tag[0] === 'icon');\n if (icons.length !== 1) return undefined;\n const [, hash, mimeType] = icons[0];\n if (icons[0].length !== 3 || !HASH.test(hash)) return undefined;\n if (mimeType !== 'image/png' && mimeType !== 'image/jpeg' && mimeType !== 'image/webp') {\n return undefined;\n }\n // A declaration is not verified image data. Hosts retain generic artwork until\n // both its hash and decoded image format have been checked independently.\n return { sha256: hash, mimeType };\n}\n\n/** Parse the single-artifact schema from NIP-5D #2303, 020cb8b. */\nexport function parseCurrentManifest(event: NostrEvent): NappletManifest {\n const tags = event.tags;\n const artifactHash = single(tags, 'x');\n if (!artifactHash || !HASH.test(artifactHash)) invalid('invalid artifact x hash');\n if (typeof event.content !== 'string' || !event.content.trim()) {\n invalid('manifest content must be a non-empty plain-text description');\n }\n const dTag = single(tags, 'd');\n if (event.kind === 35129 ? dTag === undefined : dTag !== undefined) {\n invalid('exactly one d tag is required only on named manifests');\n }\n const parent = single(tags, 'a');\n const root = single(tags, 'A');\n for (const address of [parent, root]) {\n if (address === undefined) continue;\n if (event.kind !== 5129 || !/^(35129:[a-f0-9]{64}:.+|15129:[a-f0-9]{64}:)$/.test(address)) {\n invalid('lineage must be a napplet address on a snapshot');\n }\n }\n // NAP-INTENT advertisements are optional routing hints. A malformed z/i tag\n // contributes no contract and never invalidates an otherwise verified artifact.\n const archetypeSlugs = [...new Set(tags\n .filter((tag) => tag[0] === 'z' && tag.length === 2 && SLUG.test(tag[1]))\n .map((tag) => tag[1]))];\n const archetypes = intentDeclarations(tags, archetypeSlugs);\n const intents = archetypes.map(({ convention: identity, params: parameters }) => ({ identity, parameters }));\n return {\n format: 'current',\n kind: event.kind,\n pubkey: event.pubkey,\n dTag: dTag ?? '',\n catalogId: getNappletCatalogId(event),\n artifactHash,\n aggregateHash: artifactHash,\n paths: [{ path: '/index.html', sha256: artifactHash }],\n servers: tags.filter((tag) => tag[0] === 'server' && tag[1]).map((tag) => tag[1]),\n requires: declarations(tags, 'R', DOMAIN),\n optional: declarations(tags, 'O', DOMAIN),\n archetypeSlugs,\n intents,\n archetypes,\n title: single(tags, 'title'),\n description: event.content,\n source: single(tags, 'source'),\n icon: iconFromTags(tags),\n parent,\n root,\n };\n}\n","import type { NostrEvent } from 'nostr-tools';\nimport type { NappletManifest } from './index.js';\n\nconst MIB = 1024 * 1024;\n\n/** Versioned Cache Storage namespace for verified napplet artifacts. */\nexport const NAPPLET_ARTIFACT_CACHE_NAME = 'kehto:napplet-artifacts:v1';\nconst SCHEMA_VERSION = 1;\nconst DEFAULT_BASE_URL = 'https://kehto.invalid';\nconst DEFAULT_UNKNOWN_BUDGET_BYTES = 32 * MIB;\nconst DEFAULT_SOFT_CEILING_BYTES = 128 * MIB;\nconst DEFAULT_HARD_CEILING_BYTES = 256 * MIB;\nconst DEFAULT_FOREGROUND_TTL_MS = 15 * 60 * 1000;\nconst DEFAULT_BACKGROUND_TTL_MS = 60 * 60 * 1000;\n\nexport interface NappletCacheDiagnostic {\n /** Diagnostic category for cache open/read/write/delete/prune/storage-estimate failures. */\n type:\n | 'cache-open-failed'\n | 'cache-read-failed'\n | 'cache-write-failed'\n | 'cache-delete-failed'\n | 'cache-prune-failed'\n | 'storage-estimate-unavailable';\n /** Human-readable diagnostic detail suitable for logs. */\n message: string;\n /** Original thrown value, when one exists. */\n cause?: unknown;\n}\n\n/** Metadata tracked for one cached napplet artifact blob. */\nexport interface NappletBlobIndexEntry {\n /** Blob byte length. */\n size: number;\n /** Last read or write timestamp in milliseconds. */\n lastAccessed: number;\n /** Number of cached aggregate records currently referencing this blob. */\n refCount: number;\n}\n\n/** Metadata tracked for one verified aggregate cache record. */\nexport interface NappletAggregateIndexEntry {\n /** Manifest `d` tag. */\n dTag: string;\n /** Verified NIP-5A aggregate hash. */\n aggregateHash: string;\n /** Internal cache key derived from `dTag` and `aggregateHash`. */\n key: string;\n /** SHA-256 hashes of blobs referenced by this aggregate. */\n blobHashes: string[];\n /** Approximate stored aggregate-record byte size. */\n size: number;\n /** Last read or write timestamp in milliseconds. */\n lastAccessed: number;\n /** Coordinate cache key that currently points at this aggregate, if any. */\n coordinateKey?: string;\n /** Reserved pin marker for hosts that want to protect an aggregate. */\n pinned?: boolean;\n}\n\n/** Cached mapping from a NIP-5D coordinate to a verified aggregate. */\nexport interface CachedCoordinate {\n /** Internal coordinate key in the form `kind:pubkey:dTag`. */\n key: string;\n /** Napplet manifest event kind. */\n kind: number;\n /** Manifest author public key. */\n pubkey: string;\n /** Manifest `d` tag. */\n dTag: string;\n /** Verified aggregate hash for the coordinate. */\n aggregateHash: string;\n /** Timestamp when this coordinate last resolved successfully. */\n lastResolvedAt: number;\n /** Foreground refresh TTL in milliseconds. */\n foregroundTtlMs: number;\n /** Background refresh TTL in milliseconds. */\n backgroundTtlMs: number;\n}\n\n/** Serialized Cache Storage index for verified napplet artifacts. */\nexport interface NappletArtifactCacheIndex {\n /** Index schema version. */\n schemaVersion: number;\n /** Last index update timestamp in milliseconds. */\n updatedAt: number;\n /** Blob entries keyed by SHA-256 hash. */\n blobs: Record<string, NappletBlobIndexEntry>;\n /** Aggregate entries keyed by the internal aggregate key. */\n aggregates: Record<string, NappletAggregateIndexEntry>;\n /** Coordinate entries keyed by {@link coordinateKey}. */\n coordinates: Record<string, CachedCoordinate>;\n}\n\ninterface AggregateRecord {\n schemaVersion: number;\n dTag: string;\n aggregateHash: string;\n manifest: {\n kind: number;\n pubkey: string;\n dTag: string;\n paths: Array<{ path: string; sha256: string }>;\n requires: string[];\n title?: string;\n description?: string;\n };\n indexHtml: string;\n blobHashes: string[];\n writtenAt: number;\n}\n\nexport interface WriteVerifiedResolutionInput {\n /** Verified NIP-5D manifest event. */\n event: NostrEvent;\n /** Parsed manifest whose aggregate and paths have already been verified. */\n manifest: NappletManifest;\n /** File bytes keyed by manifest path. */\n files: ReadonlyMap<string, Uint8Array>;\n /** Verified `/index.html` bytes decoded as text. */\n indexHtml: string;\n /** Timestamp override for deterministic tests. */\n now?: number;\n}\n\n/** Freshness controls for reading a cached coordinate. */\nexport interface CoordinateFreshnessOptions {\n /** Timestamp override in milliseconds. */\n now?: number;\n /** Use the background TTL instead of the foreground TTL. */\n background?: boolean;\n}\n\n/** Cache interface for verified NIP-5D napplet artifacts. */\nexport interface NappletArtifactCache {\n /** Read one cached artifact blob by SHA-256 hash. */\n readBlob(sha256Hex: string): Promise<Uint8Array | undefined>;\n /** Delete one cached artifact blob by SHA-256 hash. */\n deleteBlob(sha256Hex: string): Promise<void>;\n /** Write a verified manifest resolution and all referenced blobs. */\n writeVerifiedResolution(input: WriteVerifiedResolutionInput): Promise<void>;\n /** Cache the latest aggregate for a NIP-5D coordinate. */\n writeCoordinate(input: {\n kind: number;\n pubkey: string;\n dTag: string;\n aggregateHash: string;\n lastResolvedAt?: number;\n foregroundTtlMs?: number;\n backgroundTtlMs?: number;\n }): Promise<void>;\n /** Read a cached coordinate with a computed freshness flag. */\n getCoordinate(input: {\n kind: number;\n pubkey: string;\n dTag: string;\n }, options?: CoordinateFreshnessOptions): Promise<(CachedCoordinate & { fresh: boolean }) | undefined>;\n /** Update aggregate/blob last-access timestamps. */\n touchAggregate(dTag: string, aggregateHash: string, now?: number): Promise<void>;\n /** Protect an aggregate from normal pruning while it is in active use. */\n markAggregateActive(dTag: string, aggregateHash: string): void;\n /** Release an aggregate previously marked active. */\n releaseAggregateActive(dTag: string, aggregateHash: string): void;\n /** Prune cache contents to the configured storage budget. */\n prune(now?: number): Promise<void>;\n}\n\n/** Constructor options for {@link CacheStorageNappletArtifactCache}. */\nexport interface CacheStorageNappletArtifactCacheOptions {\n /** Cache Storage implementation; defaults to `globalThis.caches`. */\n cacheStorage?: CacheStorage;\n /** Storage manager used for quota estimates; defaults to `navigator.storage`. */\n storage?: StorageManager;\n /** Cache Storage namespace. */\n cacheName?: string;\n /** Base URL used to construct synthetic cache keys. */\n baseUrl?: string;\n /** Clock used for timestamps. */\n now?: () => number;\n /** Diagnostic callback for non-fatal cache failures. */\n onDiagnostic?: (diagnostic: NappletCacheDiagnostic) => void;\n /** Budget used when storage estimates are unavailable. */\n unknownBudgetBytes?: number;\n /** Soft cache ceiling in bytes. */\n softCeilingBytes?: number;\n /** Hard cache ceiling in bytes. */\n hardCeilingBytes?: number;\n}\n\n/** Options for opening the browser Cache Storage artifact cache. */\nexport interface OpenNappletArtifactCacheOptions extends CacheStorageNappletArtifactCacheOptions {\n /** Return `undefined` when quota estimates are unavailable. */\n requireStorageEstimate?: boolean;\n}\n\nfunction emptyIndex(now: number): NappletArtifactCacheIndex {\n return {\n schemaVersion: SCHEMA_VERSION,\n updatedAt: now,\n blobs: {},\n aggregates: {},\n coordinates: {},\n };\n}\n\nfunction aggregateKey(dTag: string, aggregateHash: string): string {\n return `${encodeURIComponent(dTag)}:${aggregateHash}`;\n}\n\n/**\n * Build the stable index key for a NIP-5D coordinate.\n *\n * @param kind - Manifest event kind.\n * @param pubkey - Manifest author public key.\n * @param dTag - Manifest `d` tag.\n * @returns Cache key in the form `kind:pubkey:dTag`.\n */\nexport function coordinateKey(kind: number, pubkey: string, dTag: string): string {\n return `${kind}:${pubkey}:${encodeURIComponent(dTag)}`;\n}\n\n/**\n * Check whether a cached coordinate is still inside its foreground/background TTL.\n *\n * @param coordinate - Cached coordinate to inspect.\n * @param options - Optional clock and TTL mode overrides.\n * @returns `true` when the coordinate can be used without a network refresh.\n */\nexport function isCoordinateFresh(\n coordinate: CachedCoordinate,\n options: CoordinateFreshnessOptions = {},\n): boolean {\n const now = options.now ?? Date.now();\n const ttl = options.background ? coordinate.backgroundTtlMs : coordinate.foregroundTtlMs;\n return ttl === Number.POSITIVE_INFINITY || now - coordinate.lastResolvedAt <= ttl;\n}\n\nfunction byteLength(bytes: Uint8Array): number {\n return bytes.byteLength;\n}\n\nfunction encodeKeyPart(value: string): string {\n return encodeURIComponent(value).replaceAll('%2F', '%252F');\n}\n\nfunction cloneBytes(bytes: Uint8Array): Uint8Array {\n return new Uint8Array(bytes);\n}\n\nfunction responseBody(bytes: Uint8Array): ArrayBuffer {\n return cloneBytes(bytes).buffer as ArrayBuffer;\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;\n}\n\nfunction resolveBaseUrl(baseUrl?: string): string {\n if (baseUrl) return normalizeBaseUrl(baseUrl);\n const maybeLocation = globalThis as typeof globalThis & { location?: { origin?: string } };\n if (maybeLocation.location?.origin) return normalizeBaseUrl(maybeLocation.location.origin);\n return normalizeBaseUrl(DEFAULT_BASE_URL);\n}\n\nasync function responseBytes(response: Response): Promise<Uint8Array> {\n return new Uint8Array(await response.arrayBuffer());\n}\n\nasync function responseJson<T>(response: Response): Promise<T> {\n return response.json() as Promise<T>;\n}\n\nfunction refCounts(index: NappletArtifactCacheIndex): Record<string, number> {\n const counts: Record<string, number> = {};\n for (const aggregate of Object.values(index.aggregates)) {\n for (const hash of aggregate.blobHashes) counts[hash] = (counts[hash] ?? 0) + 1;\n }\n return counts;\n}\n\nfunction cachedSize(index: NappletArtifactCacheIndex): number {\n const blobBytes = Object.values(index.blobs).reduce((sum, entry) => sum + entry.size, 0);\n const aggregateBytes = Object.values(index.aggregates).reduce((sum, entry) => sum + entry.size, 0);\n return blobBytes + aggregateBytes;\n}\n\n/**\n * Browser Cache Storage implementation of {@link NappletArtifactCache}.\n *\n * It stores verified blobs and aggregate records under synthetic URLs, keeps a\n * small JSON index, and prunes according to quota-derived budgets.\n */\nexport class CacheStorageNappletArtifactCache implements NappletArtifactCache {\n private readonly cacheStorage: CacheStorage;\n private readonly storage?: StorageManager;\n private readonly cacheName: string;\n private readonly baseUrl: string;\n private readonly now: () => number;\n private readonly activeAggregates = new Set<string>();\n private readonly onDiagnostic?: (diagnostic: NappletCacheDiagnostic) => void;\n private readonly unknownBudgetBytes: number;\n private readonly softCeilingBytes: number;\n private readonly hardCeilingBytes: number;\n private cachePromise?: Promise<Cache>;\n\n constructor(options: CacheStorageNappletArtifactCacheOptions = {}) {\n const maybeCacheStorage = options.cacheStorage ?? (globalThis as typeof globalThis & { caches?: CacheStorage }).caches;\n if (!maybeCacheStorage) {\n throw new Error('Cache Storage is unavailable');\n }\n this.cacheStorage = maybeCacheStorage;\n this.storage = options.storage ?? (globalThis.navigator as Navigator | undefined)?.storage;\n this.cacheName = options.cacheName ?? NAPPLET_ARTIFACT_CACHE_NAME;\n this.baseUrl = resolveBaseUrl(options.baseUrl);\n this.now = options.now ?? Date.now;\n this.onDiagnostic = options.onDiagnostic;\n this.unknownBudgetBytes = options.unknownBudgetBytes ?? DEFAULT_UNKNOWN_BUDGET_BYTES;\n this.softCeilingBytes = options.softCeilingBytes ?? DEFAULT_SOFT_CEILING_BYTES;\n this.hardCeilingBytes = options.hardCeilingBytes ?? DEFAULT_HARD_CEILING_BYTES;\n }\n\n async readBlob(sha256Hex: string): Promise<Uint8Array | undefined> {\n try {\n const cache = await this.open();\n const response = await cache.match(this.blobUrl(sha256Hex));\n if (!response) return undefined;\n const bytes = await responseBytes(response);\n const index = await this.readIndex(cache);\n const entry = index.blobs[sha256Hex];\n if (entry) {\n entry.lastAccessed = this.now();\n index.updatedAt = entry.lastAccessed;\n await this.writeIndex(cache, index);\n }\n return bytes;\n } catch (cause) {\n this.diagnostic({ type: 'cache-read-failed', message: `failed to read blob ${sha256Hex}`, cause });\n return undefined;\n }\n }\n\n async deleteBlob(sha256Hex: string): Promise<void> {\n try {\n const cache = await this.open();\n await cache.delete(this.blobUrl(sha256Hex));\n const index = await this.readIndex(cache);\n delete index.blobs[sha256Hex];\n index.updatedAt = this.now();\n await this.writeIndex(cache, index);\n } catch (cause) {\n this.diagnostic({ type: 'cache-delete-failed', message: `failed to delete blob ${sha256Hex}`, cause });\n }\n }\n\n async writeVerifiedResolution(input: WriteVerifiedResolutionInput): Promise<void> {\n try {\n await this.writeVerifiedResolutionOnce(input);\n } catch (firstCause) {\n try {\n await this.prune(input.now ?? this.now());\n await this.writeVerifiedResolutionOnce(input);\n } catch (cause) {\n this.diagnostic({\n type: 'cache-write-failed',\n message: 'failed to write verified napplet artifacts after prune retry',\n cause: cause ?? firstCause,\n });\n }\n }\n }\n\n async writeCoordinate(input: {\n kind: number;\n pubkey: string;\n dTag: string;\n aggregateHash: string;\n lastResolvedAt?: number;\n foregroundTtlMs?: number;\n backgroundTtlMs?: number;\n }): Promise<void> {\n const cache = await this.open();\n const now = this.now();\n const index = await this.readIndex(cache);\n const key = coordinateKey(input.kind, input.pubkey, input.dTag);\n index.coordinates[key] = {\n key,\n kind: input.kind,\n pubkey: input.pubkey,\n dTag: input.dTag,\n aggregateHash: input.aggregateHash,\n lastResolvedAt: input.lastResolvedAt ?? now,\n foregroundTtlMs: input.foregroundTtlMs ?? DEFAULT_FOREGROUND_TTL_MS,\n backgroundTtlMs: input.backgroundTtlMs ?? DEFAULT_BACKGROUND_TTL_MS,\n };\n index.updatedAt = now;\n await this.writeIndex(cache, index);\n }\n\n async getCoordinate(\n input: { kind: number; pubkey: string; dTag: string },\n options: CoordinateFreshnessOptions = {},\n ): Promise<(CachedCoordinate & { fresh: boolean }) | undefined> {\n const cache = await this.open();\n const index = await this.readIndex(cache);\n const coordinate = index.coordinates[coordinateKey(input.kind, input.pubkey, input.dTag)];\n if (!coordinate) return undefined;\n return { ...coordinate, fresh: isCoordinateFresh(coordinate, options) };\n }\n\n async touchAggregate(dTag: string, aggregateHash: string, now: number = this.now()): Promise<void> {\n const cache = await this.open();\n const index = await this.readIndex(cache);\n const key = aggregateKey(dTag, aggregateHash);\n const aggregate = index.aggregates[key];\n if (!aggregate) return;\n aggregate.lastAccessed = now;\n for (const hash of aggregate.blobHashes) {\n if (index.blobs[hash]) index.blobs[hash].lastAccessed = now;\n }\n index.updatedAt = now;\n await this.writeIndex(cache, index);\n }\n\n markAggregateActive(dTag: string, aggregateHash: string): void {\n this.activeAggregates.add(aggregateKey(dTag, aggregateHash));\n }\n\n releaseAggregateActive(dTag: string, aggregateHash: string): void {\n this.activeAggregates.delete(aggregateKey(dTag, aggregateHash));\n }\n\n async prune(now: number = this.now()): Promise<void> {\n try {\n const cache = await this.open();\n const index = await this.readIndex(cache);\n let size = cachedSize(index);\n const budget = await this.budgetBytes();\n const hardBudget = await this.hardBudgetBytes();\n const shouldPrune = size > budget || await this.originPressureHigh();\n if (!shouldPrune) return;\n\n this.rewriteRefCounts(index);\n\n for (const [hash, blob] of Object.entries(index.blobs)) {\n if (size <= budget) break;\n if (blob.refCount > 0) continue;\n await cache.delete(this.blobUrl(hash));\n delete index.blobs[hash];\n size -= blob.size;\n }\n\n const currentAggregateHashes = new Set(\n Object.values(index.coordinates).map((coordinate) => coordinate.aggregateHash),\n );\n const aggregates = Object.values(index.aggregates)\n .filter((entry) => !this.activeAggregates.has(entry.key))\n .sort((a, b) => {\n const currentA = currentAggregateHashes.has(a.aggregateHash) ? 1 : 0;\n const currentB = currentAggregateHashes.has(b.aggregateHash) ? 1 : 0;\n if (currentA !== currentB) return currentA - currentB;\n return a.lastAccessed - b.lastAccessed;\n });\n\n for (const aggregate of aggregates) {\n if (size <= budget) break;\n await cache.delete(this.aggregateUrl(aggregate.key));\n delete index.aggregates[aggregate.key];\n size -= aggregate.size;\n this.rewriteRefCounts(index);\n }\n\n if (size > hardBudget) {\n const active = Object.values(index.aggregates)\n .filter((entry) => this.activeAggregates.has(entry.key))\n .sort((a, b) => a.lastAccessed - b.lastAccessed);\n for (const aggregate of active) {\n if (size <= hardBudget) break;\n await cache.delete(this.aggregateUrl(aggregate.key));\n delete index.aggregates[aggregate.key];\n size -= aggregate.size;\n }\n }\n\n this.rewriteRefCounts(index);\n for (const [hash, blob] of Object.entries(index.blobs)) {\n if (blob.refCount === 0) {\n await cache.delete(this.blobUrl(hash));\n delete index.blobs[hash];\n }\n }\n index.updatedAt = now;\n await this.writeIndex(cache, index);\n } catch (cause) {\n this.diagnostic({ type: 'cache-prune-failed', message: 'failed to prune napplet artifact cache', cause });\n }\n }\n\n async snapshotIndex(): Promise<NappletArtifactCacheIndex> {\n return this.readIndex(await this.open());\n }\n\n private async writeVerifiedResolutionOnce(input: WriteVerifiedResolutionInput): Promise<void> {\n const cache = await this.open();\n const now = input.now ?? this.now();\n const index = await this.readIndex(cache);\n const blobHashes: string[] = [];\n\n for (const pathEntry of input.manifest.paths) {\n const bytes = input.files.get(pathEntry.path);\n if (!bytes) continue;\n blobHashes.push(pathEntry.sha256);\n await cache.put(this.blobUrl(pathEntry.sha256), new Response(responseBody(bytes)));\n index.blobs[pathEntry.sha256] = {\n size: byteLength(bytes),\n lastAccessed: now,\n refCount: index.blobs[pathEntry.sha256]?.refCount ?? 0,\n };\n }\n\n const key = aggregateKey(input.manifest.dTag, input.manifest.aggregateHash);\n const record: AggregateRecord = {\n schemaVersion: SCHEMA_VERSION,\n dTag: input.manifest.dTag,\n aggregateHash: input.manifest.aggregateHash,\n manifest: {\n kind: input.manifest.kind,\n pubkey: input.manifest.pubkey,\n dTag: input.manifest.dTag,\n paths: input.manifest.paths.map((entry) => ({ path: entry.path, sha256: entry.sha256 })),\n requires: [...input.manifest.requires],\n ...(input.manifest.title === undefined ? {} : { title: input.manifest.title }),\n ...(input.manifest.description === undefined ? {} : { description: input.manifest.description }),\n },\n indexHtml: input.indexHtml,\n blobHashes,\n writtenAt: now,\n };\n const aggregateBytes = new TextEncoder().encode(JSON.stringify(record));\n await cache.put(this.aggregateUrl(key), new Response(responseBody(aggregateBytes), {\n headers: { 'content-type': 'application/json' },\n }));\n\n const coordKey = coordinateKey(input.manifest.kind, input.manifest.pubkey, input.manifest.dTag);\n index.aggregates[key] = {\n dTag: input.manifest.dTag,\n aggregateHash: input.manifest.aggregateHash,\n key,\n blobHashes,\n size: byteLength(aggregateBytes),\n lastAccessed: now,\n coordinateKey: coordKey,\n };\n index.coordinates[coordKey] = {\n key: coordKey,\n kind: input.manifest.kind,\n pubkey: input.manifest.pubkey,\n dTag: input.manifest.dTag,\n aggregateHash: input.manifest.aggregateHash,\n lastResolvedAt: now,\n foregroundTtlMs: DEFAULT_FOREGROUND_TTL_MS,\n backgroundTtlMs: DEFAULT_BACKGROUND_TTL_MS,\n };\n\n this.rewriteRefCounts(index);\n index.updatedAt = now;\n await this.writeIndex(cache, index);\n await this.prune(now);\n }\n\n private async open(): Promise<Cache> {\n this.cachePromise ??= this.cacheStorage.open(this.cacheName);\n return this.cachePromise;\n }\n\n private indexUrl(): string {\n return new URL('__kehto_napplet_cache/index.json', this.baseUrl).toString();\n }\n\n private blobUrl(hash: string): string {\n return new URL(`__kehto_napplet_cache/blob/${encodeKeyPart(hash)}`, this.baseUrl).toString();\n }\n\n private aggregateUrl(key: string): string {\n return new URL(`__kehto_napplet_cache/aggregate/${encodeKeyPart(key)}.json`, this.baseUrl).toString();\n }\n\n private async readIndex(cache: Cache): Promise<NappletArtifactCacheIndex> {\n const response = await cache.match(this.indexUrl());\n if (!response) return emptyIndex(this.now());\n try {\n const index = await responseJson<NappletArtifactCacheIndex>(response);\n if (index.schemaVersion !== SCHEMA_VERSION) return emptyIndex(this.now());\n return index;\n } catch {\n return emptyIndex(this.now());\n }\n }\n\n private async writeIndex(cache: Cache, index: NappletArtifactCacheIndex): Promise<void> {\n await cache.put(this.indexUrl(), new Response(JSON.stringify(index), {\n headers: { 'content-type': 'application/json' },\n }));\n }\n\n private rewriteRefCounts(index: NappletArtifactCacheIndex): void {\n const counts = refCounts(index);\n for (const [hash, blob] of Object.entries(index.blobs)) {\n blob.refCount = counts[hash] ?? 0;\n }\n }\n\n private async budgetBytes(): Promise<number> {\n const estimate = await this.estimateStorage();\n if (!estimate?.quota) return this.unknownBudgetBytes;\n return Math.min(this.softCeilingBytes, Math.floor(estimate.quota * 0.1));\n }\n\n private async hardBudgetBytes(): Promise<number> {\n const estimate = await this.estimateStorage();\n if (!estimate?.quota) return this.unknownBudgetBytes;\n return Math.min(this.hardCeilingBytes, Math.floor(estimate.quota * 0.2));\n }\n\n private async originPressureHigh(): Promise<boolean> {\n const estimate = await this.estimateStorage();\n if (!estimate?.quota || estimate.usage === undefined) return false;\n return estimate.usage / estimate.quota >= 0.8;\n }\n\n private async estimateStorage(): Promise<StorageEstimate | undefined> {\n if (!this.storage?.estimate) return undefined;\n try {\n return this.storage.estimate();\n } catch (cause) {\n this.diagnostic({\n type: 'storage-estimate-unavailable',\n message: 'storage estimate failed; using unknown quota cache budget',\n cause,\n });\n return undefined;\n }\n }\n\n private diagnostic(diagnostic: NappletCacheDiagnostic): void {\n this.onDiagnostic?.(diagnostic);\n }\n}\n\n/**\n * Open the browser Cache Storage artifact cache when the host supports it.\n *\n * @param options - Cache construction options and diagnostics callback.\n * @returns A cache instance, or `undefined` when Cache Storage is unavailable.\n */\nexport async function openNappletArtifactCache(\n options: OpenNappletArtifactCacheOptions = {},\n): Promise<CacheStorageNappletArtifactCache | undefined> {\n const cacheStorage = options.cacheStorage ?? (globalThis as typeof globalThis & { caches?: CacheStorage }).caches;\n if (!cacheStorage) return undefined;\n const storage = options.storage ?? (globalThis.navigator as Navigator | undefined)?.storage;\n if (options.requireStorageEstimate && !storage?.estimate) {\n options.onDiagnostic?.({\n type: 'storage-estimate-unavailable',\n message: 'storage estimates are unavailable; using network-only loading',\n });\n return undefined;\n }\n try {\n const cache = new CacheStorageNappletArtifactCache({ ...options, cacheStorage, storage });\n await cache.snapshotIndex();\n return cache;\n } catch (cause) {\n options.onDiagnostic?.({\n type: 'cache-open-failed',\n message: 'Cache Storage napplet artifact cache could not be opened',\n cause,\n });\n return undefined;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { PathEntry, aggregateTagValue, computeAggregateHash, pathEntriesFromTags, verifyAggregate } from './5a/index.js';
2
- export { CacheStorageNappletArtifactCache, CacheStorageNappletArtifactCacheOptions, CachedCoordinate, CoordinateFreshnessOptions, NAPPLET_ARTIFACT_CACHE_NAME, NAPPLET_KINDS, NAPPLET_KIND_NAMED, NAPPLET_KIND_ROOT, NAPPLET_KIND_SNAPSHOT, NappletAggregateIndexEntry, NappletArtifactCache, NappletArtifactCacheIndex, NappletBlobIndexEntry, NappletCacheDiagnostic, NappletManifest, NappletResolutionError, NappletResolutionErrorCode, OpenNappletArtifactCacheOptions, ResolveNappletOptions, ResolvedNapplet, WriteVerifiedResolutionInput, coordinateKey, fetchBlob, isCoordinateFresh, isNappletManifestKind, openNappletArtifactCache, parseNappletManifest, resolveNapplet, verifyBlobHash, verifyManifestSignature } from './5d/index.js';
2
+ export { CacheStorageNappletArtifactCache, CacheStorageNappletArtifactCacheOptions, CachedCoordinate, CoordinateFreshnessOptions, NAPPLET_ARTIFACT_CACHE_NAME, NAPPLET_KINDS, NAPPLET_KIND_NAMED, NAPPLET_KIND_ROOT, NAPPLET_KIND_SNAPSHOT, NappletAggregateIndexEntry, NappletArtifactCache, NappletArtifactCacheIndex, NappletBlobIndexEntry, NappletCacheDiagnostic, NappletManifest, NappletResolutionError, NappletResolutionErrorCode, OpenNappletArtifactCacheOptions, ResolveNappletOptions, ResolvedNapplet, WriteVerifiedResolutionInput, coordinateKey, fetchBlob, getNappletCatalogId, isCoordinateFresh, isNappletManifestKind, openNappletArtifactCache, parseNappletManifest, resolveNapplet, verifyBlobHash, verifyManifestSignature } from './5d/index.js';
3
3
  export { ITEM_TAGS, LIST_KINDS, Nip44Decryptor, Nip51List, SET_KINDS, decryptPrivateItems, getTagValues, isListKind, isSetKind, listKindName, parseList } from './51/index.js';
4
4
  export { Nip65Registry, RelayEntry, createNip65Registry, parseNip65RelayList, selectReadRelays, selectWriteRelays } from './65/index.js';
5
5
  export { DEFAULT_RELAY_ATTRIBUTE_GROUPS, Nip66Aggregator, Nip66AggregatorOptions, Nip66Filter, Nip66RelayAttributes, Nip66RelayPool, RelayAttributeGroups, RelayAttributeMatcher, createNip66Aggregator } from './66/index.js';
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  NappletResolutionError,
20
20
  coordinateKey,
21
21
  fetchBlob,
22
+ getNappletCatalogId,
22
23
  isCoordinateFresh,
23
24
  isNappletManifestKind,
24
25
  openNappletArtifactCache,
@@ -26,7 +27,7 @@ import {
26
27
  resolveNapplet,
27
28
  verifyBlobHash,
28
29
  verifyManifestSignature
29
- } from "./chunk-F6ABV5KZ.js";
30
+ } from "./chunk-AKQIPGRC.js";
30
31
  import {
31
32
  aggregateTagValue,
32
33
  computeAggregateHash,
@@ -75,6 +76,7 @@ export {
75
76
  createNip66Aggregator,
76
77
  decryptPrivateItems,
77
78
  fetchBlob,
79
+ getNappletCatalogId,
78
80
  getTagValues,
79
81
  handlesKind,
80
82
  isCoordinateFresh,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kehto/nip",
3
- "version": "0.5.2",
3
+ "version": "0.7.0",
4
4
  "description": "Tree-shakable bundle of unique Nostr NIP utilities for napplet runtimes — each NIP at its own subpath (e.g. @kehto/nip/66)",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/5d/index.ts","../src/5d/artifact-cache.ts"],"sourcesContent":["import type { NostrEvent } from 'nostr-tools';\nimport { verifyEvent } from 'nostr-tools/pure';\nimport { sha256 } from '@noble/hashes/sha2.js';\nimport { bytesToHex } from '@noble/hashes/utils.js';\nimport {\n computeAggregateHash,\n pathEntriesFromTags,\n aggregateTagValue,\n type PathEntry,\n} from '../5a/index.js';\nimport type { NappletArtifactCache } from './artifact-cache.js';\nexport {\n CacheStorageNappletArtifactCache,\n NAPPLET_ARTIFACT_CACHE_NAME,\n coordinateKey,\n isCoordinateFresh,\n openNappletArtifactCache,\n type CachedCoordinate,\n type CacheStorageNappletArtifactCacheOptions,\n type CoordinateFreshnessOptions,\n type NappletAggregateIndexEntry,\n type NappletArtifactCacheIndex,\n type NappletArtifactCache,\n type NappletBlobIndexEntry,\n type NappletCacheDiagnostic,\n type OpenNappletArtifactCacheOptions,\n type WriteVerifiedResolutionInput,\n} from './artifact-cache.js';\n\n/**\n * `@kehto/nip/5d` — NIP-5D napplet manifest resolution.\n *\n * NIP-5D (`dskvr/nips` branch `nip/5d`) defines content-addressed \"napplet\"\n * web applets published as Nostr events that reference their files by hash\n * (NIP-5A `path` tags + an aggregate `x` tag). A runtime resolves a napplet by:\n *\n * 1. verifying the manifest event's signature,\n * 2. recomputing the NIP-5A aggregate from its `path` tags and checking the\n * `[\"x\",\"<hex>\",\"aggregate\"]` tag,\n * 3. fetching each referenced blob (e.g. from Blossom) and verifying its hash,\n * 4. assembling the verified `/index.html`.\n *\n * The napplet's identity is the `(dTag, aggregateHash)` tuple **computed** from\n * these verified bytes — it is never accepted from a host or gateway.\n *\n * @module\n */\n\n/** Snapshot manifest — regular event, immutable point-in-time release. */\nexport const NAPPLET_KIND_SNAPSHOT = 5129;\n/** Root manifest — replaceable event, an author's latest unnamed napplet. */\nexport const NAPPLET_KIND_ROOT = 15129;\n/** Named manifest — addressable event (carries a `d` tag identifier). */\nexport const NAPPLET_KIND_NAMED = 35129;\n\n/** All three NIP-5D napplet manifest kinds. */\nexport const NAPPLET_KINDS: readonly number[] = [\n NAPPLET_KIND_SNAPSHOT,\n NAPPLET_KIND_ROOT,\n NAPPLET_KIND_NAMED,\n];\n\n/**\n * Whether `kind` is one of the three NIP-5D napplet manifest kinds.\n *\n * @param kind - A Nostr event kind\n * @returns `true` for `5129` / `15129` / `35129`\n */\nexport function isNappletManifestKind(kind: number): boolean {\n return NAPPLET_KINDS.includes(kind);\n}\n\n/**\n * A parsed NIP-5D napplet manifest. All fields are derived from the manifest\n * event's tags; the aggregate is the declared `x` tag (verify it separately\n * with {@link resolveNapplet} or `@kehto/nip/5a` `verifyAggregate`).\n */\nexport interface NappletManifest {\n /** Manifest event kind (`5129` / `15129` / `35129`). */\n kind: number;\n /** Author hex public key. */\n pubkey: string;\n /** Named-napplet `d` identifier, or `''` for root/snapshot manifests. */\n dTag: string;\n /** File path entries from `path` tags. */\n paths: PathEntry[];\n /** Declared aggregate hash from the `[\"x\",\"<hex>\",\"aggregate\"]` tag. */\n aggregateHash: string;\n /** Blossom server URL hints from `server` tags. */\n servers: string[];\n /** Short NAP capability names from `requires` tags. */\n requires: string[];\n /**\n * Convention contracts this napplet fulfills, from `archetype` manifest tags.\n */\n archetypes: Array<{ slug: string; convention: string }>;\n /** Optional human title. */\n title?: string;\n /** Optional human description. */\n description?: string;\n /** Optional upstream source URL from the `source` tag. */\n source?: string;\n}\n\n/** Error codes for every napplet resolution failure path. */\nexport type NappletResolutionErrorCode =\n | 'invalid-signature'\n | 'invalid-manifest'\n | 'aggregate-mismatch'\n | 'blob-hash-mismatch'\n | 'blob-unavailable'\n | 'missing-index';\n\n/**\n * Thrown on any napplet resolution/verification failure. The `code` field\n * identifies which guard rejected, so callers can fail closed without parsing\n * the message.\n */\nexport class NappletResolutionError extends Error {\n readonly code: NappletResolutionErrorCode;\n constructor(code: NappletResolutionErrorCode, message: string) {\n super(message);\n this.name = 'NappletResolutionError';\n this.code = code;\n }\n}\n\nfunction firstTagValue(tags: readonly (readonly string[])[], name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name && typeof tag[1] === 'string' && tag[1].length > 0) return tag[1];\n }\n return undefined;\n}\n\nfunction allTagValues(tags: readonly (readonly string[])[], name: string): string[] {\n const out: string[] = [];\n for (const tag of tags) {\n if (tag[0] === name && typeof tag[1] === 'string' && tag[1].length > 0) out.push(tag[1]);\n }\n return out;\n}\n\nfunction archetypesFromTags(\n tags: readonly (readonly string[])[],\n): Array<{ slug: string; convention: string }> {\n const out: Array<{ slug: string; convention: string }> = [];\n for (const tag of tags) {\n if (tag[0] !== 'archetype') continue;\n const slug = tag[1];\n if (typeof slug !== 'string' || !/^[a-z0-9][a-z0-9-]*$/.test(slug)) {\n throw new NappletResolutionError(\n 'invalid-manifest',\n 'archetype slug must contain lowercase letters, numbers, and hyphens',\n );\n }\n\n const convention = tag[2];\n if (typeof convention !== 'string' || convention.length === 0) {\n throw new NappletResolutionError('invalid-manifest', 'archetype convention is required');\n }\n if (/^NAP-\\d+$/.test(convention)) {\n throw new NappletResolutionError(\n 'invalid-manifest',\n 'numbered NAP identifier is not an archetype convention',\n );\n }\n if (!/^napplet:[^/?#\\s]+\\/[^/?#\\s]+$/.test(convention)) {\n throw new NappletResolutionError(\n 'invalid-manifest',\n 'archetype convention must be a queryless napplet:<archetype>/<intent> identity',\n );\n }\n // NAP-INTENT keeps a routing archetype and a payload convention\n // orthogonal: one convention may serve multiple archetypes and vice versa.\n\n if (tag.length !== 3) {\n throw new NappletResolutionError(\n 'invalid-manifest',\n 'archetype tags must contain exactly slug and convention',\n );\n }\n out.push({ slug, convention });\n }\n return out;\n}\n\n/**\n * Parse a NIP-5D manifest event into a {@link NappletManifest}.\n *\n * Does not verify the signature, aggregate, or blobs — use {@link resolveNapplet}\n * for full verification.\n *\n * @param event - A NIP-5D manifest event (`5129` / `15129` / `35129`)\n * @returns The parsed manifest\n * @throws {@link NappletResolutionError} `invalid-manifest` for a non-napplet\n * kind, missing `path` tags, or a missing aggregate `x` tag\n */\nexport function parseNappletManifest(event: NostrEvent): NappletManifest {\n if (!isNappletManifestKind(event.kind)) {\n throw new NappletResolutionError('invalid-manifest', `not a NIP-5D napplet kind: ${event.kind}`);\n }\n const paths = pathEntriesFromTags(event.tags);\n if (paths.length === 0) {\n throw new NappletResolutionError('invalid-manifest', 'manifest has no path tags');\n }\n const aggregateHash = aggregateTagValue(event.tags);\n if (!aggregateHash) {\n throw new NappletResolutionError('invalid-manifest', 'manifest has no aggregate x tag');\n }\n const dTag = firstTagValue(event.tags, 'd');\n if (event.kind !== NAPPLET_KIND_NAMED && dTag !== undefined) {\n throw new NappletResolutionError('invalid-manifest', 'only named manifests may carry a d tag');\n }\n return {\n kind: event.kind,\n pubkey: event.pubkey,\n dTag: dTag ?? '',\n paths,\n aggregateHash,\n servers: allTagValues(event.tags, 'server'),\n requires: allTagValues(event.tags, 'requires'),\n archetypes: archetypesFromTags(event.tags),\n title: firstTagValue(event.tags, 'title'),\n description: firstTagValue(event.tags, 'description'),\n source: firstTagValue(event.tags, 'source'),\n };\n}\n\n/**\n * Verify a manifest event's Nostr signature (id + schnorr sig).\n *\n * @param event - A manifest event\n * @returns `true` if the event is internally consistent and validly signed\n */\nexport function verifyManifestSignature(event: NostrEvent): boolean {\n try {\n return verifyEvent(event);\n } catch {\n return false;\n }\n}\n\n/**\n * Whether `bytes` hash to the expected lowercase-hex SHA-256.\n *\n * @param bytes - The blob bytes\n * @param sha256Hex - Expected lowercase-hex SHA-256\n * @returns `true` only on an exact match\n */\nexport function verifyBlobHash(bytes: Uint8Array, sha256Hex: string): boolean {\n return bytesToHex(sha256(bytes)) === sha256Hex;\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.endsWith('/') ? url.slice(0, -1) : url;\n}\n\n/**\n * Fetch a blob by SHA-256 from a list of Blossom servers, returning the first\n * server's bytes whose hash matches. The returned bytes are always re-verified\n * against `sha256Hex` — servers (and gateways) are never trusted.\n *\n * @param servers - Candidate Blossom server base URLs, tried in order\n * @param sha256Hex - The blob's lowercase-hex SHA-256\n * @param fetchBytes - Fetches raw bytes for a URL (`<server>/<sha256>`)\n * @returns The verified blob bytes\n * @throws {@link NappletResolutionError} `blob-unavailable` if no server serves\n * a hash-matching blob\n */\nexport async function fetchBlob(\n servers: readonly string[],\n sha256Hex: string,\n fetchBytes: (url: string) => Promise<Uint8Array>,\n): Promise<Uint8Array> {\n for (const server of servers) {\n const url = `${stripTrailingSlash(server)}/${sha256Hex}`;\n try {\n const bytes = await fetchBytes(url);\n if (verifyBlobHash(bytes, sha256Hex)) return bytes;\n } catch {\n // try the next server\n }\n }\n throw new NappletResolutionError('blob-unavailable', `no server served blob ${sha256Hex}`);\n}\n\n/** A fully verified napplet, ready to inject via `iframe.srcdoc`. */\nexport interface ResolvedNapplet {\n /** Computed `d` identifier (`''` for root/snapshot). */\n dTag: string;\n /** Computed (and verified) aggregate hash — the content address. */\n aggregateHash: string;\n /** Verified file bytes keyed by manifest path. */\n files: Map<string, Uint8Array>;\n /** The verified `/index.html` decoded to text. */\n indexHtml: string;\n /** The parsed manifest. */\n manifest: NappletManifest;\n}\n\n/** Options for {@link resolveNapplet}. */\nexport interface ResolveNappletOptions {\n /** The candidate manifest event (resolved from relays by the caller). */\n event: NostrEvent;\n /**\n * Fetch raw blob bytes by SHA-256. Backed by Blossom (or a gateway) by the\n * caller. The bytes are re-verified against the hash here, so the fetcher is\n * untrusted.\n */\n fetchBlob: (sha256Hex: string, servers: readonly string[]) => Promise<Uint8Array>;\n /** Decode blob bytes to text for `indexHtml` (default UTF-8). */\n textDecode?: (bytes: Uint8Array) => string;\n /**\n * Optional verified artifact cache. Cache hits are still re-verified against\n * the manifest hash before use; cache writes happen only after the signature,\n * aggregate, and every blob hash have been verified.\n */\n cache?: NappletArtifactCache;\n}\n\nconst INDEX_PATHS = ['/index.html', 'index.html', '/'];\n\nfunction defaultDecode(bytes: Uint8Array): string {\n return new TextDecoder().decode(bytes);\n}\n\n/**\n * Resolve a napplet end-to-end from a candidate manifest event: verify the\n * signature, parse the manifest, verify the NIP-5A aggregate, fetch and verify\n * every blob, then assemble the verified `/index.html`.\n *\n * The returned `(dTag, aggregateHash)` is computed from the verified bytes and\n * is the napplet's identity. Any failure throws a {@link NappletResolutionError}\n * — the caller must fail closed (never render unverified bytes).\n *\n * @param options - {@link ResolveNappletOptions}\n * @returns The {@link ResolvedNapplet}\n * @throws {@link NappletResolutionError} on signature, manifest, aggregate,\n * blob-hash, blob-availability, or missing-index failures\n *\n * @example\n * ```ts\n * import { resolveNapplet } from '@kehto/nip/5d';\n * const napplet = await resolveNapplet({ event, fetchBlob });\n * iframe.srcdoc = napplet.indexHtml; // sandbox=\"allow-scripts\", opaque origin\n * ```\n */\nexport async function resolveNapplet(options: ResolveNappletOptions): Promise<ResolvedNapplet> {\n const { event, fetchBlob: fetchBlobBytes, textDecode = defaultDecode, cache } = options;\n\n if (!verifyManifestSignature(event)) {\n throw new NappletResolutionError('invalid-signature', 'manifest signature is invalid');\n }\n\n const manifest = parseNappletManifest(event);\n\n const recomputed = computeAggregateHash(manifest.paths);\n if (recomputed !== manifest.aggregateHash) {\n throw new NappletResolutionError(\n 'aggregate-mismatch',\n `recomputed aggregate ${recomputed} != manifest ${manifest.aggregateHash}`,\n );\n }\n\n const files = new Map<string, Uint8Array>();\n for (const entry of manifest.paths) {\n let bytes = await cache?.readBlob(entry.sha256);\n if (bytes && !verifyBlobHash(bytes, entry.sha256)) {\n await cache?.deleteBlob(entry.sha256);\n bytes = undefined;\n }\n bytes ??= await fetchBlobBytes(entry.sha256, manifest.servers);\n if (!verifyBlobHash(bytes, entry.sha256)) {\n throw new NappletResolutionError(\n 'blob-hash-mismatch',\n `blob for ${entry.path} does not match hash ${entry.sha256}`,\n );\n }\n files.set(entry.path, bytes);\n }\n\n const indexEntry = manifest.paths.find((e) => INDEX_PATHS.includes(e.path));\n if (!indexEntry) {\n throw new NappletResolutionError('missing-index', 'manifest has no /index.html entry');\n }\n\n const indexHtml = textDecode(files.get(indexEntry.path)!);\n await cache?.writeVerifiedResolution({ event, manifest, files, indexHtml });\n\n return {\n dTag: manifest.dTag,\n aggregateHash: manifest.aggregateHash,\n files,\n indexHtml,\n manifest,\n };\n}\n","import type { NostrEvent } from 'nostr-tools';\nimport type { NappletManifest } from './index.js';\n\nconst MIB = 1024 * 1024;\n\n/** Versioned Cache Storage namespace for verified napplet artifacts. */\nexport const NAPPLET_ARTIFACT_CACHE_NAME = 'kehto:napplet-artifacts:v1';\nconst SCHEMA_VERSION = 1;\nconst DEFAULT_BASE_URL = 'https://kehto.invalid';\nconst DEFAULT_UNKNOWN_BUDGET_BYTES = 32 * MIB;\nconst DEFAULT_SOFT_CEILING_BYTES = 128 * MIB;\nconst DEFAULT_HARD_CEILING_BYTES = 256 * MIB;\nconst DEFAULT_FOREGROUND_TTL_MS = 15 * 60 * 1000;\nconst DEFAULT_BACKGROUND_TTL_MS = 60 * 60 * 1000;\n\nexport interface NappletCacheDiagnostic {\n /** Diagnostic category for cache open/read/write/delete/prune/storage-estimate failures. */\n type:\n | 'cache-open-failed'\n | 'cache-read-failed'\n | 'cache-write-failed'\n | 'cache-delete-failed'\n | 'cache-prune-failed'\n | 'storage-estimate-unavailable';\n /** Human-readable diagnostic detail suitable for logs. */\n message: string;\n /** Original thrown value, when one exists. */\n cause?: unknown;\n}\n\n/** Metadata tracked for one cached napplet artifact blob. */\nexport interface NappletBlobIndexEntry {\n /** Blob byte length. */\n size: number;\n /** Last read or write timestamp in milliseconds. */\n lastAccessed: number;\n /** Number of cached aggregate records currently referencing this blob. */\n refCount: number;\n}\n\n/** Metadata tracked for one verified aggregate cache record. */\nexport interface NappletAggregateIndexEntry {\n /** Manifest `d` tag. */\n dTag: string;\n /** Verified NIP-5A aggregate hash. */\n aggregateHash: string;\n /** Internal cache key derived from `dTag` and `aggregateHash`. */\n key: string;\n /** SHA-256 hashes of blobs referenced by this aggregate. */\n blobHashes: string[];\n /** Approximate stored aggregate-record byte size. */\n size: number;\n /** Last read or write timestamp in milliseconds. */\n lastAccessed: number;\n /** Coordinate cache key that currently points at this aggregate, if any. */\n coordinateKey?: string;\n /** Reserved pin marker for hosts that want to protect an aggregate. */\n pinned?: boolean;\n}\n\n/** Cached mapping from a NIP-5D coordinate to a verified aggregate. */\nexport interface CachedCoordinate {\n /** Internal coordinate key in the form `kind:pubkey:dTag`. */\n key: string;\n /** Napplet manifest event kind. */\n kind: number;\n /** Manifest author public key. */\n pubkey: string;\n /** Manifest `d` tag. */\n dTag: string;\n /** Verified aggregate hash for the coordinate. */\n aggregateHash: string;\n /** Timestamp when this coordinate last resolved successfully. */\n lastResolvedAt: number;\n /** Foreground refresh TTL in milliseconds. */\n foregroundTtlMs: number;\n /** Background refresh TTL in milliseconds. */\n backgroundTtlMs: number;\n}\n\n/** Serialized Cache Storage index for verified napplet artifacts. */\nexport interface NappletArtifactCacheIndex {\n /** Index schema version. */\n schemaVersion: number;\n /** Last index update timestamp in milliseconds. */\n updatedAt: number;\n /** Blob entries keyed by SHA-256 hash. */\n blobs: Record<string, NappletBlobIndexEntry>;\n /** Aggregate entries keyed by the internal aggregate key. */\n aggregates: Record<string, NappletAggregateIndexEntry>;\n /** Coordinate entries keyed by {@link coordinateKey}. */\n coordinates: Record<string, CachedCoordinate>;\n}\n\ninterface AggregateRecord {\n schemaVersion: number;\n dTag: string;\n aggregateHash: string;\n manifest: {\n kind: number;\n pubkey: string;\n dTag: string;\n paths: Array<{ path: string; sha256: string }>;\n requires: string[];\n title?: string;\n description?: string;\n };\n indexHtml: string;\n blobHashes: string[];\n writtenAt: number;\n}\n\nexport interface WriteVerifiedResolutionInput {\n /** Verified NIP-5D manifest event. */\n event: NostrEvent;\n /** Parsed manifest whose aggregate and paths have already been verified. */\n manifest: NappletManifest;\n /** File bytes keyed by manifest path. */\n files: ReadonlyMap<string, Uint8Array>;\n /** Verified `/index.html` bytes decoded as text. */\n indexHtml: string;\n /** Timestamp override for deterministic tests. */\n now?: number;\n}\n\n/** Freshness controls for reading a cached coordinate. */\nexport interface CoordinateFreshnessOptions {\n /** Timestamp override in milliseconds. */\n now?: number;\n /** Use the background TTL instead of the foreground TTL. */\n background?: boolean;\n}\n\n/** Cache interface for verified NIP-5D napplet artifacts. */\nexport interface NappletArtifactCache {\n /** Read one cached artifact blob by SHA-256 hash. */\n readBlob(sha256Hex: string): Promise<Uint8Array | undefined>;\n /** Delete one cached artifact blob by SHA-256 hash. */\n deleteBlob(sha256Hex: string): Promise<void>;\n /** Write a verified manifest resolution and all referenced blobs. */\n writeVerifiedResolution(input: WriteVerifiedResolutionInput): Promise<void>;\n /** Cache the latest aggregate for a NIP-5D coordinate. */\n writeCoordinate(input: {\n kind: number;\n pubkey: string;\n dTag: string;\n aggregateHash: string;\n lastResolvedAt?: number;\n foregroundTtlMs?: number;\n backgroundTtlMs?: number;\n }): Promise<void>;\n /** Read a cached coordinate with a computed freshness flag. */\n getCoordinate(input: {\n kind: number;\n pubkey: string;\n dTag: string;\n }, options?: CoordinateFreshnessOptions): Promise<(CachedCoordinate & { fresh: boolean }) | undefined>;\n /** Update aggregate/blob last-access timestamps. */\n touchAggregate(dTag: string, aggregateHash: string, now?: number): Promise<void>;\n /** Protect an aggregate from normal pruning while it is in active use. */\n markAggregateActive(dTag: string, aggregateHash: string): void;\n /** Release an aggregate previously marked active. */\n releaseAggregateActive(dTag: string, aggregateHash: string): void;\n /** Prune cache contents to the configured storage budget. */\n prune(now?: number): Promise<void>;\n}\n\n/** Constructor options for {@link CacheStorageNappletArtifactCache}. */\nexport interface CacheStorageNappletArtifactCacheOptions {\n /** Cache Storage implementation; defaults to `globalThis.caches`. */\n cacheStorage?: CacheStorage;\n /** Storage manager used for quota estimates; defaults to `navigator.storage`. */\n storage?: StorageManager;\n /** Cache Storage namespace. */\n cacheName?: string;\n /** Base URL used to construct synthetic cache keys. */\n baseUrl?: string;\n /** Clock used for timestamps. */\n now?: () => number;\n /** Diagnostic callback for non-fatal cache failures. */\n onDiagnostic?: (diagnostic: NappletCacheDiagnostic) => void;\n /** Budget used when storage estimates are unavailable. */\n unknownBudgetBytes?: number;\n /** Soft cache ceiling in bytes. */\n softCeilingBytes?: number;\n /** Hard cache ceiling in bytes. */\n hardCeilingBytes?: number;\n}\n\n/** Options for opening the browser Cache Storage artifact cache. */\nexport interface OpenNappletArtifactCacheOptions extends CacheStorageNappletArtifactCacheOptions {\n /** Return `undefined` when quota estimates are unavailable. */\n requireStorageEstimate?: boolean;\n}\n\nfunction emptyIndex(now: number): NappletArtifactCacheIndex {\n return {\n schemaVersion: SCHEMA_VERSION,\n updatedAt: now,\n blobs: {},\n aggregates: {},\n coordinates: {},\n };\n}\n\nfunction aggregateKey(dTag: string, aggregateHash: string): string {\n return `${encodeURIComponent(dTag)}:${aggregateHash}`;\n}\n\n/**\n * Build the stable index key for a NIP-5D coordinate.\n *\n * @param kind - Manifest event kind.\n * @param pubkey - Manifest author public key.\n * @param dTag - Manifest `d` tag.\n * @returns Cache key in the form `kind:pubkey:dTag`.\n */\nexport function coordinateKey(kind: number, pubkey: string, dTag: string): string {\n return `${kind}:${pubkey}:${encodeURIComponent(dTag)}`;\n}\n\n/**\n * Check whether a cached coordinate is still inside its foreground/background TTL.\n *\n * @param coordinate - Cached coordinate to inspect.\n * @param options - Optional clock and TTL mode overrides.\n * @returns `true` when the coordinate can be used without a network refresh.\n */\nexport function isCoordinateFresh(\n coordinate: CachedCoordinate,\n options: CoordinateFreshnessOptions = {},\n): boolean {\n const now = options.now ?? Date.now();\n const ttl = options.background ? coordinate.backgroundTtlMs : coordinate.foregroundTtlMs;\n return ttl === Number.POSITIVE_INFINITY || now - coordinate.lastResolvedAt <= ttl;\n}\n\nfunction byteLength(bytes: Uint8Array): number {\n return bytes.byteLength;\n}\n\nfunction encodeKeyPart(value: string): string {\n return encodeURIComponent(value).replaceAll('%2F', '%252F');\n}\n\nfunction cloneBytes(bytes: Uint8Array): Uint8Array {\n return new Uint8Array(bytes);\n}\n\nfunction responseBody(bytes: Uint8Array): ArrayBuffer {\n return cloneBytes(bytes).buffer as ArrayBuffer;\n}\n\nfunction normalizeBaseUrl(baseUrl: string): string {\n return baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;\n}\n\nfunction resolveBaseUrl(baseUrl?: string): string {\n if (baseUrl) return normalizeBaseUrl(baseUrl);\n const maybeLocation = globalThis as typeof globalThis & { location?: { origin?: string } };\n if (maybeLocation.location?.origin) return normalizeBaseUrl(maybeLocation.location.origin);\n return normalizeBaseUrl(DEFAULT_BASE_URL);\n}\n\nasync function responseBytes(response: Response): Promise<Uint8Array> {\n return new Uint8Array(await response.arrayBuffer());\n}\n\nasync function responseJson<T>(response: Response): Promise<T> {\n return response.json() as Promise<T>;\n}\n\nfunction refCounts(index: NappletArtifactCacheIndex): Record<string, number> {\n const counts: Record<string, number> = {};\n for (const aggregate of Object.values(index.aggregates)) {\n for (const hash of aggregate.blobHashes) counts[hash] = (counts[hash] ?? 0) + 1;\n }\n return counts;\n}\n\nfunction cachedSize(index: NappletArtifactCacheIndex): number {\n const blobBytes = Object.values(index.blobs).reduce((sum, entry) => sum + entry.size, 0);\n const aggregateBytes = Object.values(index.aggregates).reduce((sum, entry) => sum + entry.size, 0);\n return blobBytes + aggregateBytes;\n}\n\n/**\n * Browser Cache Storage implementation of {@link NappletArtifactCache}.\n *\n * It stores verified blobs and aggregate records under synthetic URLs, keeps a\n * small JSON index, and prunes according to quota-derived budgets.\n */\nexport class CacheStorageNappletArtifactCache implements NappletArtifactCache {\n private readonly cacheStorage: CacheStorage;\n private readonly storage?: StorageManager;\n private readonly cacheName: string;\n private readonly baseUrl: string;\n private readonly now: () => number;\n private readonly activeAggregates = new Set<string>();\n private readonly onDiagnostic?: (diagnostic: NappletCacheDiagnostic) => void;\n private readonly unknownBudgetBytes: number;\n private readonly softCeilingBytes: number;\n private readonly hardCeilingBytes: number;\n private cachePromise?: Promise<Cache>;\n\n constructor(options: CacheStorageNappletArtifactCacheOptions = {}) {\n const maybeCacheStorage = options.cacheStorage ?? (globalThis as typeof globalThis & { caches?: CacheStorage }).caches;\n if (!maybeCacheStorage) {\n throw new Error('Cache Storage is unavailable');\n }\n this.cacheStorage = maybeCacheStorage;\n this.storage = options.storage ?? (globalThis.navigator as Navigator | undefined)?.storage;\n this.cacheName = options.cacheName ?? NAPPLET_ARTIFACT_CACHE_NAME;\n this.baseUrl = resolveBaseUrl(options.baseUrl);\n this.now = options.now ?? Date.now;\n this.onDiagnostic = options.onDiagnostic;\n this.unknownBudgetBytes = options.unknownBudgetBytes ?? DEFAULT_UNKNOWN_BUDGET_BYTES;\n this.softCeilingBytes = options.softCeilingBytes ?? DEFAULT_SOFT_CEILING_BYTES;\n this.hardCeilingBytes = options.hardCeilingBytes ?? DEFAULT_HARD_CEILING_BYTES;\n }\n\n async readBlob(sha256Hex: string): Promise<Uint8Array | undefined> {\n try {\n const cache = await this.open();\n const response = await cache.match(this.blobUrl(sha256Hex));\n if (!response) return undefined;\n const bytes = await responseBytes(response);\n const index = await this.readIndex(cache);\n const entry = index.blobs[sha256Hex];\n if (entry) {\n entry.lastAccessed = this.now();\n index.updatedAt = entry.lastAccessed;\n await this.writeIndex(cache, index);\n }\n return bytes;\n } catch (cause) {\n this.diagnostic({ type: 'cache-read-failed', message: `failed to read blob ${sha256Hex}`, cause });\n return undefined;\n }\n }\n\n async deleteBlob(sha256Hex: string): Promise<void> {\n try {\n const cache = await this.open();\n await cache.delete(this.blobUrl(sha256Hex));\n const index = await this.readIndex(cache);\n delete index.blobs[sha256Hex];\n index.updatedAt = this.now();\n await this.writeIndex(cache, index);\n } catch (cause) {\n this.diagnostic({ type: 'cache-delete-failed', message: `failed to delete blob ${sha256Hex}`, cause });\n }\n }\n\n async writeVerifiedResolution(input: WriteVerifiedResolutionInput): Promise<void> {\n try {\n await this.writeVerifiedResolutionOnce(input);\n } catch (firstCause) {\n try {\n await this.prune(input.now ?? this.now());\n await this.writeVerifiedResolutionOnce(input);\n } catch (cause) {\n this.diagnostic({\n type: 'cache-write-failed',\n message: 'failed to write verified napplet artifacts after prune retry',\n cause: cause ?? firstCause,\n });\n }\n }\n }\n\n async writeCoordinate(input: {\n kind: number;\n pubkey: string;\n dTag: string;\n aggregateHash: string;\n lastResolvedAt?: number;\n foregroundTtlMs?: number;\n backgroundTtlMs?: number;\n }): Promise<void> {\n const cache = await this.open();\n const now = this.now();\n const index = await this.readIndex(cache);\n const key = coordinateKey(input.kind, input.pubkey, input.dTag);\n index.coordinates[key] = {\n key,\n kind: input.kind,\n pubkey: input.pubkey,\n dTag: input.dTag,\n aggregateHash: input.aggregateHash,\n lastResolvedAt: input.lastResolvedAt ?? now,\n foregroundTtlMs: input.foregroundTtlMs ?? DEFAULT_FOREGROUND_TTL_MS,\n backgroundTtlMs: input.backgroundTtlMs ?? DEFAULT_BACKGROUND_TTL_MS,\n };\n index.updatedAt = now;\n await this.writeIndex(cache, index);\n }\n\n async getCoordinate(\n input: { kind: number; pubkey: string; dTag: string },\n options: CoordinateFreshnessOptions = {},\n ): Promise<(CachedCoordinate & { fresh: boolean }) | undefined> {\n const cache = await this.open();\n const index = await this.readIndex(cache);\n const coordinate = index.coordinates[coordinateKey(input.kind, input.pubkey, input.dTag)];\n if (!coordinate) return undefined;\n return { ...coordinate, fresh: isCoordinateFresh(coordinate, options) };\n }\n\n async touchAggregate(dTag: string, aggregateHash: string, now: number = this.now()): Promise<void> {\n const cache = await this.open();\n const index = await this.readIndex(cache);\n const key = aggregateKey(dTag, aggregateHash);\n const aggregate = index.aggregates[key];\n if (!aggregate) return;\n aggregate.lastAccessed = now;\n for (const hash of aggregate.blobHashes) {\n if (index.blobs[hash]) index.blobs[hash].lastAccessed = now;\n }\n index.updatedAt = now;\n await this.writeIndex(cache, index);\n }\n\n markAggregateActive(dTag: string, aggregateHash: string): void {\n this.activeAggregates.add(aggregateKey(dTag, aggregateHash));\n }\n\n releaseAggregateActive(dTag: string, aggregateHash: string): void {\n this.activeAggregates.delete(aggregateKey(dTag, aggregateHash));\n }\n\n async prune(now: number = this.now()): Promise<void> {\n try {\n const cache = await this.open();\n const index = await this.readIndex(cache);\n let size = cachedSize(index);\n const budget = await this.budgetBytes();\n const hardBudget = await this.hardBudgetBytes();\n const shouldPrune = size > budget || await this.originPressureHigh();\n if (!shouldPrune) return;\n\n this.rewriteRefCounts(index);\n\n for (const [hash, blob] of Object.entries(index.blobs)) {\n if (size <= budget) break;\n if (blob.refCount > 0) continue;\n await cache.delete(this.blobUrl(hash));\n delete index.blobs[hash];\n size -= blob.size;\n }\n\n const currentAggregateHashes = new Set(\n Object.values(index.coordinates).map((coordinate) => coordinate.aggregateHash),\n );\n const aggregates = Object.values(index.aggregates)\n .filter((entry) => !this.activeAggregates.has(entry.key))\n .sort((a, b) => {\n const currentA = currentAggregateHashes.has(a.aggregateHash) ? 1 : 0;\n const currentB = currentAggregateHashes.has(b.aggregateHash) ? 1 : 0;\n if (currentA !== currentB) return currentA - currentB;\n return a.lastAccessed - b.lastAccessed;\n });\n\n for (const aggregate of aggregates) {\n if (size <= budget) break;\n await cache.delete(this.aggregateUrl(aggregate.key));\n delete index.aggregates[aggregate.key];\n size -= aggregate.size;\n this.rewriteRefCounts(index);\n }\n\n if (size > hardBudget) {\n const active = Object.values(index.aggregates)\n .filter((entry) => this.activeAggregates.has(entry.key))\n .sort((a, b) => a.lastAccessed - b.lastAccessed);\n for (const aggregate of active) {\n if (size <= hardBudget) break;\n await cache.delete(this.aggregateUrl(aggregate.key));\n delete index.aggregates[aggregate.key];\n size -= aggregate.size;\n }\n }\n\n this.rewriteRefCounts(index);\n for (const [hash, blob] of Object.entries(index.blobs)) {\n if (blob.refCount === 0) {\n await cache.delete(this.blobUrl(hash));\n delete index.blobs[hash];\n }\n }\n index.updatedAt = now;\n await this.writeIndex(cache, index);\n } catch (cause) {\n this.diagnostic({ type: 'cache-prune-failed', message: 'failed to prune napplet artifact cache', cause });\n }\n }\n\n async snapshotIndex(): Promise<NappletArtifactCacheIndex> {\n return this.readIndex(await this.open());\n }\n\n private async writeVerifiedResolutionOnce(input: WriteVerifiedResolutionInput): Promise<void> {\n const cache = await this.open();\n const now = input.now ?? this.now();\n const index = await this.readIndex(cache);\n const blobHashes: string[] = [];\n\n for (const pathEntry of input.manifest.paths) {\n const bytes = input.files.get(pathEntry.path);\n if (!bytes) continue;\n blobHashes.push(pathEntry.sha256);\n await cache.put(this.blobUrl(pathEntry.sha256), new Response(responseBody(bytes)));\n index.blobs[pathEntry.sha256] = {\n size: byteLength(bytes),\n lastAccessed: now,\n refCount: index.blobs[pathEntry.sha256]?.refCount ?? 0,\n };\n }\n\n const key = aggregateKey(input.manifest.dTag, input.manifest.aggregateHash);\n const record: AggregateRecord = {\n schemaVersion: SCHEMA_VERSION,\n dTag: input.manifest.dTag,\n aggregateHash: input.manifest.aggregateHash,\n manifest: {\n kind: input.manifest.kind,\n pubkey: input.manifest.pubkey,\n dTag: input.manifest.dTag,\n paths: input.manifest.paths.map((entry) => ({ path: entry.path, sha256: entry.sha256 })),\n requires: [...input.manifest.requires],\n ...(input.manifest.title === undefined ? {} : { title: input.manifest.title }),\n ...(input.manifest.description === undefined ? {} : { description: input.manifest.description }),\n },\n indexHtml: input.indexHtml,\n blobHashes,\n writtenAt: now,\n };\n const aggregateBytes = new TextEncoder().encode(JSON.stringify(record));\n await cache.put(this.aggregateUrl(key), new Response(responseBody(aggregateBytes), {\n headers: { 'content-type': 'application/json' },\n }));\n\n const coordKey = coordinateKey(input.manifest.kind, input.manifest.pubkey, input.manifest.dTag);\n index.aggregates[key] = {\n dTag: input.manifest.dTag,\n aggregateHash: input.manifest.aggregateHash,\n key,\n blobHashes,\n size: byteLength(aggregateBytes),\n lastAccessed: now,\n coordinateKey: coordKey,\n };\n index.coordinates[coordKey] = {\n key: coordKey,\n kind: input.manifest.kind,\n pubkey: input.manifest.pubkey,\n dTag: input.manifest.dTag,\n aggregateHash: input.manifest.aggregateHash,\n lastResolvedAt: now,\n foregroundTtlMs: DEFAULT_FOREGROUND_TTL_MS,\n backgroundTtlMs: DEFAULT_BACKGROUND_TTL_MS,\n };\n\n this.rewriteRefCounts(index);\n index.updatedAt = now;\n await this.writeIndex(cache, index);\n await this.prune(now);\n }\n\n private async open(): Promise<Cache> {\n this.cachePromise ??= this.cacheStorage.open(this.cacheName);\n return this.cachePromise;\n }\n\n private indexUrl(): string {\n return new URL('__kehto_napplet_cache/index.json', this.baseUrl).toString();\n }\n\n private blobUrl(hash: string): string {\n return new URL(`__kehto_napplet_cache/blob/${encodeKeyPart(hash)}`, this.baseUrl).toString();\n }\n\n private aggregateUrl(key: string): string {\n return new URL(`__kehto_napplet_cache/aggregate/${encodeKeyPart(key)}.json`, this.baseUrl).toString();\n }\n\n private async readIndex(cache: Cache): Promise<NappletArtifactCacheIndex> {\n const response = await cache.match(this.indexUrl());\n if (!response) return emptyIndex(this.now());\n try {\n const index = await responseJson<NappletArtifactCacheIndex>(response);\n if (index.schemaVersion !== SCHEMA_VERSION) return emptyIndex(this.now());\n return index;\n } catch {\n return emptyIndex(this.now());\n }\n }\n\n private async writeIndex(cache: Cache, index: NappletArtifactCacheIndex): Promise<void> {\n await cache.put(this.indexUrl(), new Response(JSON.stringify(index), {\n headers: { 'content-type': 'application/json' },\n }));\n }\n\n private rewriteRefCounts(index: NappletArtifactCacheIndex): void {\n const counts = refCounts(index);\n for (const [hash, blob] of Object.entries(index.blobs)) {\n blob.refCount = counts[hash] ?? 0;\n }\n }\n\n private async budgetBytes(): Promise<number> {\n const estimate = await this.estimateStorage();\n if (!estimate?.quota) return this.unknownBudgetBytes;\n return Math.min(this.softCeilingBytes, Math.floor(estimate.quota * 0.1));\n }\n\n private async hardBudgetBytes(): Promise<number> {\n const estimate = await this.estimateStorage();\n if (!estimate?.quota) return this.unknownBudgetBytes;\n return Math.min(this.hardCeilingBytes, Math.floor(estimate.quota * 0.2));\n }\n\n private async originPressureHigh(): Promise<boolean> {\n const estimate = await this.estimateStorage();\n if (!estimate?.quota || estimate.usage === undefined) return false;\n return estimate.usage / estimate.quota >= 0.8;\n }\n\n private async estimateStorage(): Promise<StorageEstimate | undefined> {\n if (!this.storage?.estimate) return undefined;\n try {\n return this.storage.estimate();\n } catch (cause) {\n this.diagnostic({\n type: 'storage-estimate-unavailable',\n message: 'storage estimate failed; using unknown quota cache budget',\n cause,\n });\n return undefined;\n }\n }\n\n private diagnostic(diagnostic: NappletCacheDiagnostic): void {\n this.onDiagnostic?.(diagnostic);\n }\n}\n\n/**\n * Open the browser Cache Storage artifact cache when the host supports it.\n *\n * @param options - Cache construction options and diagnostics callback.\n * @returns A cache instance, or `undefined` when Cache Storage is unavailable.\n */\nexport async function openNappletArtifactCache(\n options: OpenNappletArtifactCacheOptions = {},\n): Promise<CacheStorageNappletArtifactCache | undefined> {\n const cacheStorage = options.cacheStorage ?? (globalThis as typeof globalThis & { caches?: CacheStorage }).caches;\n if (!cacheStorage) return undefined;\n const storage = options.storage ?? (globalThis.navigator as Navigator | undefined)?.storage;\n if (options.requireStorageEstimate && !storage?.estimate) {\n options.onDiagnostic?.({\n type: 'storage-estimate-unavailable',\n message: 'storage estimates are unavailable; using network-only loading',\n });\n return undefined;\n }\n try {\n const cache = new CacheStorageNappletArtifactCache({ ...options, cacheStorage, storage });\n await cache.snapshotIndex();\n return cache;\n } catch (cause) {\n options.onDiagnostic?.({\n type: 'cache-open-failed',\n message: 'Cache Storage napplet artifact cache could not be opened',\n cause,\n });\n return undefined;\n }\n}\n"],"mappings":"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","names":[]}