@kehto/nip 0.1.0 → 0.3.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
@@ -1,37 +1,69 @@
1
1
  # @kehto/nip
2
2
 
3
- A tree-shakable bundle of unique Nostr NIP utilities for napplet runtimes. Each
4
- NIP lives at its own subpath so consumers import only what they use; the package
5
- is `sideEffects: false`, so bundlers drop any NIP that isn't referenced.
3
+ A tree-shakable bundle of **unique** Nostr NIP utilities for napplet runtimes —
4
+ the NIPs that `nostr-tools` does *not* ship but that relay-aware runtimes
5
+ commonly need. Each NIP lives in its own folder at its own subpath, so consumers
6
+ import only what they use; the package is `sideEffects: false`, so bundlers drop
7
+ any NIP that isn't referenced.
6
8
 
7
9
  ## NIPs
8
10
 
9
11
  | Subpath | NIP | What it provides |
10
12
  |---------|-----|------------------|
11
- | `@kehto/nip/66` | NIP-66 | kind-30166 relay-discovery aggregator (`createNip66Aggregator`) |
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`) |
15
+ | [`@kehto/nip/51`](./src/51/README.md) | NIP-51 | lists & sets parser — mute/bookmarks/relay-sets/emoji-sets/… (`parseList`) |
16
+ | [`@kehto/nip/65`](./src/65/README.md) | NIP-65 | relay-list (kind 10002) parsing + outbox/inbox resolution (`createNip65Registry`) |
17
+ | [`@kehto/nip/66`](./src/66/README.md) | NIP-66 | kind-30166 relay-discovery aggregator (`createNip66Aggregator`) |
18
+ | [`@kehto/nip/89`](./src/89/README.md) | NIP-89 | app-handler discovery (kind 31989/31990) (`parseHandlerInformation`) |
12
19
 
20
+ Each subpath has its own README with full API docs and examples (linked above).
13
21
  More unique NIPs are added as their own subpath (`@kehto/nip/<n>`) over time.
14
22
 
23
+ ## Selection criteria
24
+
25
+ A NIP earns a place here only if it is:
26
+
27
+ 1. **Unique** — not already provided by `nostr-tools` (re-wrapping `nip04`,
28
+ `nip19`, `nip25`, `nip44`, `nip57`, … would be a no-op package).
29
+ 2. **Broadly needed** — likely required by many runtimes, not just one app.
30
+ 3. **Substantive** — handles a real common case in a widely-compatible way, with
31
+ no framework coupling and no module-global state.
32
+
15
33
  ## Usage
16
34
 
17
35
  ```ts
18
36
  // Import only the NIP you need — nothing else is bundled.
19
- import { createNip66Aggregator } from '@kehto/nip/66';
37
+ import { createNip65Registry } from '@kehto/nip/65';
20
38
 
21
- const aggregator = createNip66Aggregator({
22
- pool: myPoolAdapter,
23
- bootstrap: ['wss://monitor.example.com'],
24
- });
25
- aggregator.start();
39
+ const registry = createNip65Registry();
40
+ registry.ingest(kind10002Event);
41
+ const outbox = registry.resolveOutboxRelays([authorPubkey]);
26
42
  ```
27
43
 
28
44
  A convenience barrel re-export is also available at the package root
29
45
  (`@kehto/nip`), but prefer subpath imports for the smallest bundles.
30
46
 
47
+ ## Conventions
48
+
49
+ Every NIP module is:
50
+
51
+ - **Pure / framework-agnostic** — no Svelte/React, no DOM assumptions, no relay
52
+ library baked in. Relay pools, signers, and decryptors are injected via small
53
+ interfaces.
54
+ - **Multi-instance safe** — stateful helpers are closure-scoped factories
55
+ (`create*`), never module globals, so multiple runtimes in one process never
56
+ alias each other.
57
+ - **Fully documented** — all public exports carry JSDoc with `@param`,
58
+ `@returns`, and `@example`.
59
+
31
60
  ## Adding a NIP
32
61
 
33
- 1. Add `src/<n>.ts` implementing the NIP utility (framework-agnostic, no
34
- module-global state, inject relay/pool concerns via interfaces).
35
- 2. Add `src/<n>.ts` to the `tsup` `entry` array and the `./<n>` `exports` entry
36
- in `package.json`.
37
- 3. Re-export it from `src/index.ts` for the barrel.
62
+ 1. Create `src/<n>/index.ts` implementing the NIP utility (framework-agnostic, no
63
+ module-global state, inject relay/pool/crypto concerns via interfaces).
64
+ 2. Add `src/<n>/index.test.ts` with unit tests and `src/<n>/README.md` with API docs.
65
+ 3. Add `src/<n>/index.ts` to the `tsup` `entry` array and a `./<n>` entry to
66
+ `exports` in `package.json`.
67
+ 4. Re-export it from `src/index.ts` for the barrel, and add the
68
+ `@kehto/nip/<n>` alias to the repo `vitest.config.ts`.
69
+ 5. Add a row to the table above.
@@ -0,0 +1,157 @@
1
+ import { NostrEvent } from 'nostr-tools';
2
+
3
+ /**
4
+ * `@kehto/nip/51` — NIP-51 list & set parsing.
5
+ *
6
+ * NIP-51 organizes user data into two families of events whose items live in
7
+ * tags (public) and, optionally, in NIP-44-encrypted content (private):
8
+ *
9
+ * - **Standard lists** — replaceable, kinds `10000`–`10101`. One per user per
10
+ * kind (e.g. mute list, bookmarks, blocked relays, DM relays).
11
+ * - **Sets** — addressable / parameterized-replaceable, kinds `30000`–`30030`.
12
+ * Many per user, distinguished by a `d` identifier (e.g. follow sets, relay
13
+ * sets, emoji sets, curation sets).
14
+ *
15
+ * `nostr-tools` ships no NIP-51 helper. This module provides a single
16
+ * widely-compatible parser that turns either family into a structured
17
+ * {@link Nip51List}, plus item accessors and an injected-decryptor path for
18
+ * private items (so the package stays crypto-free and framework-agnostic).
19
+ */
20
+ /** Canonical NIP-51 **standard list** kinds (replaceable, one per author). */
21
+ declare const LIST_KINDS: Readonly<Record<number, string>>;
22
+ /** Canonical NIP-51 **set** kinds (addressable, many per author by `d`). */
23
+ declare const SET_KINDS: Readonly<Record<number, string>>;
24
+ /**
25
+ * Item-bearing NIP-51 tag names — the tags that carry list *content* (as
26
+ * opposed to metadata tags like `d`, `title`, `image`, `description`).
27
+ */
28
+ declare const ITEM_TAGS: readonly ["e", "p", "a", "t", "r", "word", "emoji", "relay", "g", "i", "k"];
29
+ /** Whether `kind` is a NIP-51 standard list kind (`10000`–`10101`). */
30
+ declare function isListKind(kind: number): boolean;
31
+ /** Whether `kind` is a NIP-51 set kind (`30000`–`30030`). */
32
+ declare function isSetKind(kind: number): boolean;
33
+ /**
34
+ * Friendly name for a NIP-51 list/set kind (e.g. `10000` → `'mute'`,
35
+ * `30030` → `'emoji-sets'`), or `undefined` if `kind` is not a known NIP-51 kind.
36
+ */
37
+ declare function listKindName(kind: number): string | undefined;
38
+ /**
39
+ * A parsed NIP-51 list or set.
40
+ *
41
+ * `publicItems` holds the item-bearing tags verbatim. `privateItems` is
42
+ * populated only after {@link decryptPrivateItems} runs; until then it is
43
+ * `undefined` (the encrypted payload is left untouched in {@link Nip51List.encryptedContent}).
44
+ *
45
+ * @example
46
+ * ```ts
47
+ * const list = parseList(kind10000MuteEvent);
48
+ * list.kind; // 10000
49
+ * list.type; // 'mute'
50
+ * list.family; // 'list'
51
+ * getTagValues(list.publicItems, 'p'); // muted pubkeys (public portion)
52
+ * ```
53
+ */
54
+ interface Nip51List {
55
+ /** Event kind. */
56
+ kind: number;
57
+ /** `'list'` for standard lists (10000–10101), `'set'` for sets (30000–30030), `'unknown'` otherwise. */
58
+ family: 'list' | 'set' | 'unknown';
59
+ /** Friendly kind name from {@link listKindName}, or `undefined` for unknown kinds. */
60
+ type: string | undefined;
61
+ /** Author pubkey. */
62
+ pubkey: string;
63
+ /** Set identifier from the `d` tag (sets only; `''` for standard lists / missing). */
64
+ identifier: string;
65
+ /** Addressable coordinate `kind:pubkey:d` for sets; `undefined` for standard lists. */
66
+ address: string | undefined;
67
+ /** Optional `title` tag (sets). */
68
+ title: string | undefined;
69
+ /** Optional `image` tag (sets). */
70
+ image: string | undefined;
71
+ /** Optional `description` tag (sets). */
72
+ description: string | undefined;
73
+ /** Item-bearing tags verbatim (the public portion of the list). */
74
+ publicItems: string[][];
75
+ /** Raw NIP-44-encrypted content, if any (the private portion). */
76
+ encryptedContent: string | undefined;
77
+ /** Decrypted private item tags — `undefined` until {@link decryptPrivateItems} succeeds. */
78
+ privateItems: string[][] | undefined;
79
+ }
80
+ /**
81
+ * Parse a NIP-51 list or set event into a structured {@link Nip51List}.
82
+ *
83
+ * Works for any kind: known list/set kinds get a `type` and correct `family`;
84
+ * unknown kinds still parse (`family: 'unknown'`) so forward-compatible callers
85
+ * can handle new list kinds before this table is updated.
86
+ *
87
+ * Item-bearing tags (see {@link ITEM_TAGS}) become {@link Nip51List.publicItems};
88
+ * metadata tags (`d`, `title`, `image`, `description`, …) are lifted into named
89
+ * fields. The encrypted `content` is preserved verbatim in
90
+ * {@link Nip51List.encryptedContent} — call {@link decryptPrivateItems} with a
91
+ * NIP-44 decryptor to populate {@link Nip51List.privateItems}.
92
+ *
93
+ * @param event - A NIP-51 list or set event
94
+ * @returns The structured list/set
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * import { parseList, getTagValues } from '@kehto/nip/51';
99
+ * const bookmarks = parseList(kind10003Event);
100
+ * const notes = getTagValues(bookmarks.publicItems, 'e');
101
+ * ```
102
+ */
103
+ declare function parseList(event: NostrEvent): Nip51List;
104
+ /**
105
+ * Collect the values (`tag[1]`) of every item tag with the given name.
106
+ *
107
+ * @param items - Item tags ({@link Nip51List.publicItems} or {@link Nip51List.privateItems})
108
+ * @param tagName - Tag name to filter on (e.g. `'p'`, `'e'`, `'a'`, `'t'`, `'word'`)
109
+ * @returns The second element of each matching tag (missing values skipped)
110
+ *
111
+ * @example
112
+ * ```ts
113
+ * const mutedPubkeys = getTagValues(list.publicItems, 'p');
114
+ * const mutedWords = getTagValues(list.publicItems, 'word');
115
+ * ```
116
+ */
117
+ declare function getTagValues(items: ReadonlyArray<ReadonlyArray<string>>, tagName: string): string[];
118
+ /**
119
+ * A NIP-44 decryptor: takes the event's `content` and the author pubkey and
120
+ * returns the plaintext (a JSON array of tags). Inject your signer's
121
+ * `nip44.decrypt` (e.g. a NIP-07 `window.nostr.nip44`, a bunker, or
122
+ * `nostr-tools/nip44`) so this package carries no crypto dependency.
123
+ *
124
+ * @example
125
+ * ```ts
126
+ * const decrypt: Nip44Decryptor = (content, author) =>
127
+ * window.nostr!.nip44!.decrypt(author, content);
128
+ * ```
129
+ */
130
+ type Nip44Decryptor = (content: string, authorPubkey: string) => string | Promise<string>;
131
+ /**
132
+ * Decrypt the private portion of a list/set and populate
133
+ * {@link Nip51List.privateItems}.
134
+ *
135
+ * NIP-51 stores private items as a NIP-44-encrypted JSON array of tags in the
136
+ * event `content`, encrypted to the author's own key. This helper runs the
137
+ * injected {@link Nip44Decryptor}, parses the plaintext as `string[][]`, and
138
+ * returns a **new** {@link Nip51List} with `privateItems` set (the input is not
139
+ * mutated). Lists with no encrypted content return a copy with
140
+ * `privateItems: []`.
141
+ *
142
+ * Throws if the decryptor throws or the plaintext is not a JSON tag array — so
143
+ * callers can distinguish "no private items" from "couldn't read private items".
144
+ *
145
+ * @param list - A parsed list from {@link parseList}
146
+ * @param decrypt - Injected NIP-44 decryptor
147
+ * @returns A copy of `list` with `privateItems` populated
148
+ *
149
+ * @example
150
+ * ```ts
151
+ * const withPrivate = await decryptPrivateItems(list, (c, a) => signer.nip44.decrypt(a, c));
152
+ * const privatelyMuted = getTagValues(withPrivate.privateItems!, 'p');
153
+ * ```
154
+ */
155
+ declare function decryptPrivateItems(list: Nip51List, decrypt: Nip44Decryptor): Promise<Nip51List>;
156
+
157
+ export { ITEM_TAGS, LIST_KINDS, type Nip44Decryptor, type Nip51List, SET_KINDS, decryptPrivateItems, getTagValues, isListKind, isSetKind, listKindName, parseList };
@@ -0,0 +1,23 @@
1
+ import {
2
+ ITEM_TAGS,
3
+ LIST_KINDS,
4
+ SET_KINDS,
5
+ decryptPrivateItems,
6
+ getTagValues,
7
+ isListKind,
8
+ isSetKind,
9
+ listKindName,
10
+ parseList
11
+ } from "../chunk-2XYRP2S6.js";
12
+ export {
13
+ ITEM_TAGS,
14
+ LIST_KINDS,
15
+ SET_KINDS,
16
+ decryptPrivateItems,
17
+ getTagValues,
18
+ isListKind,
19
+ isSetKind,
20
+ listKindName,
21
+ parseList
22
+ };
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,64 @@
1
+ /**
2
+ * One published file: an absolute path and the lowercase-hex SHA-256 of its
3
+ * bytes. Parsed from a NIP-5A `["path", "<abs-path>", "<sha256>"]` tag.
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * const entry: PathEntry = { path: '/index.html', sha256: '186ea5…1c99' };
8
+ * ```
9
+ */
10
+ interface PathEntry {
11
+ /** Absolute path of the file (the `path` tag's second element). */
12
+ path: string;
13
+ /** Lowercase-hex SHA-256 of the file bytes (the `path` tag's third element). */
14
+ sha256: string;
15
+ }
16
+ /**
17
+ * Compute the NIP-5A aggregate hash over a set of {@link PathEntry} files.
18
+ *
19
+ * Lines (`"<sha256> <abs-path>\n"`) are sorted before hashing, so the result is
20
+ * independent of the input order.
21
+ *
22
+ * @param entries - The file path entries (any order)
23
+ * @returns The aggregate hash as lowercase hex
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { computeAggregateHash } from '@kehto/nip/5a';
28
+ * const hex = computeAggregateHash([{ path: '/index.html', sha256: 'aa…' }]);
29
+ * ```
30
+ */
31
+ declare function computeAggregateHash(entries: readonly PathEntry[]): string;
32
+ /**
33
+ * Extract {@link PathEntry} objects from an event's tags, keeping only
34
+ * well-formed `["path", "<abs-path>", "<sha256>"]` tags in tag order.
35
+ *
36
+ * @param tags - The manifest event's tags
37
+ * @returns Parsed path entries (malformed `path` tags are skipped)
38
+ */
39
+ declare function pathEntriesFromTags(tags: readonly (readonly string[])[]): PathEntry[];
40
+ /**
41
+ * Read the aggregate hash declared by an event's `["x", "<hex>", "aggregate"]`
42
+ * tag.
43
+ *
44
+ * @param tags - The manifest event's tags
45
+ * @returns The declared aggregate hex, or `undefined` if no aggregate `x` tag
46
+ */
47
+ declare function aggregateTagValue(tags: readonly (readonly string[])[]): string | undefined;
48
+ /**
49
+ * Verify that an event's declared aggregate `x` tag equals the aggregate
50
+ * recomputed from its `path` tags.
51
+ *
52
+ * @param tags - The manifest event's tags
53
+ * @returns `true` only when an aggregate `x` tag is present and matches the
54
+ * recomputed value; `false` otherwise (mismatch or missing aggregate tag)
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import { verifyAggregate } from '@kehto/nip/5a';
59
+ * if (!verifyAggregate(manifestEvent.tags)) throw new Error('aggregate mismatch');
60
+ * ```
61
+ */
62
+ declare function verifyAggregate(tags: readonly (readonly string[])[]): boolean;
63
+
64
+ export { type PathEntry, aggregateTagValue, computeAggregateHash, pathEntriesFromTags, verifyAggregate };
@@ -0,0 +1,13 @@
1
+ import {
2
+ aggregateTagValue,
3
+ computeAggregateHash,
4
+ pathEntriesFromTags,
5
+ verifyAggregate
6
+ } from "../chunk-2J3CSGKL.js";
7
+ export {
8
+ aggregateTagValue,
9
+ computeAggregateHash,
10
+ pathEntriesFromTags,
11
+ verifyAggregate
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,170 @@
1
+ import { NostrEvent } from 'nostr-tools';
2
+ import { PathEntry } from '../5a/index.js';
3
+
4
+ /**
5
+ * `@kehto/nip/5d` — NIP-5D napplet manifest resolution.
6
+ *
7
+ * NIP-5D (`dskvr/nips` branch `nip/5d`) defines content-addressed "napplet"
8
+ * web applets published as Nostr events that reference their files by hash
9
+ * (NIP-5A `path` tags + an aggregate `x` tag). A runtime resolves a napplet by:
10
+ *
11
+ * 1. verifying the manifest event's signature,
12
+ * 2. recomputing the NIP-5A aggregate from its `path` tags and checking the
13
+ * `["x","<hex>","aggregate"]` tag,
14
+ * 3. fetching each referenced blob (e.g. from Blossom) and verifying its hash,
15
+ * 4. assembling the verified `/index.html`.
16
+ *
17
+ * The napplet's identity is the `(dTag, aggregateHash)` tuple **computed** from
18
+ * these verified bytes — it is never accepted from a host or gateway.
19
+ */
20
+ /** Snapshot manifest — regular event, immutable point-in-time release. */
21
+ declare const NAPPLET_KIND_SNAPSHOT = 5129;
22
+ /** Root manifest — replaceable event, an author's latest unnamed napplet. */
23
+ declare const NAPPLET_KIND_ROOT = 15129;
24
+ /** Named manifest — addressable event (carries a `d` tag identifier). */
25
+ declare const NAPPLET_KIND_NAMED = 35129;
26
+ /** All three NIP-5D napplet manifest kinds. */
27
+ declare const NAPPLET_KINDS: readonly number[];
28
+ /**
29
+ * Whether `kind` is one of the three NIP-5D napplet manifest kinds.
30
+ *
31
+ * @param kind - A Nostr event kind
32
+ * @returns `true` for `5129` / `15129` / `35129`
33
+ */
34
+ declare function isNappletManifestKind(kind: number): boolean;
35
+ /**
36
+ * A parsed NIP-5D napplet manifest. All fields are derived from the manifest
37
+ * event's tags; the aggregate is the declared `x` tag (verify it separately
38
+ * with {@link resolveNapplet} or `@kehto/nip/5a` `verifyAggregate`).
39
+ */
40
+ interface NappletManifest {
41
+ /** Manifest event kind (`5129` / `15129` / `35129`). */
42
+ kind: number;
43
+ /** Author hex public key. */
44
+ pubkey: string;
45
+ /** Named-napplet `d` identifier, or `''` for root/snapshot manifests. */
46
+ dTag: string;
47
+ /** File path entries from `path` tags. */
48
+ paths: PathEntry[];
49
+ /** Declared aggregate hash from the `["x","<hex>","aggregate"]` tag. */
50
+ aggregateHash: string;
51
+ /** Blossom server URL hints from `server` tags. */
52
+ servers: string[];
53
+ /** Short NAP capability names from `requires` tags. */
54
+ requires: string[];
55
+ /**
56
+ * Archetype slugs this napplet fulfills, from `archetype` manifest tags; the
57
+ * optional `nap` is the recommended default wire protocol (the 3rd tag element).
58
+ */
59
+ archetypes: Array<{
60
+ slug: string;
61
+ nap?: string;
62
+ }>;
63
+ /** Optional human title. */
64
+ title?: string;
65
+ /** Optional human description. */
66
+ description?: string;
67
+ /** Optional upstream source URL from the `source` tag. */
68
+ source?: string;
69
+ }
70
+ /** Error codes for every napplet resolution failure path. */
71
+ type NappletResolutionErrorCode = 'invalid-signature' | 'invalid-manifest' | 'aggregate-mismatch' | 'blob-hash-mismatch' | 'blob-unavailable' | 'missing-index';
72
+ /**
73
+ * Thrown on any napplet resolution/verification failure. The `code` field
74
+ * identifies which guard rejected, so callers can fail closed without parsing
75
+ * the message.
76
+ */
77
+ declare class NappletResolutionError extends Error {
78
+ readonly code: NappletResolutionErrorCode;
79
+ constructor(code: NappletResolutionErrorCode, message: string);
80
+ }
81
+ /**
82
+ * Parse a NIP-5D manifest event into a {@link NappletManifest}.
83
+ *
84
+ * Does not verify the signature, aggregate, or blobs — use {@link resolveNapplet}
85
+ * for full verification.
86
+ *
87
+ * @param event - A NIP-5D manifest event (`5129` / `15129` / `35129`)
88
+ * @returns The parsed manifest
89
+ * @throws {@link NappletResolutionError} `invalid-manifest` for a non-napplet
90
+ * kind, missing `path` tags, or a missing aggregate `x` tag
91
+ */
92
+ declare function parseNappletManifest(event: NostrEvent): NappletManifest;
93
+ /**
94
+ * Verify a manifest event's Nostr signature (id + schnorr sig).
95
+ *
96
+ * @param event - A manifest event
97
+ * @returns `true` if the event is internally consistent and validly signed
98
+ */
99
+ declare function verifyManifestSignature(event: NostrEvent): boolean;
100
+ /**
101
+ * Whether `bytes` hash to the expected lowercase-hex SHA-256.
102
+ *
103
+ * @param bytes - The blob bytes
104
+ * @param sha256Hex - Expected lowercase-hex SHA-256
105
+ * @returns `true` only on an exact match
106
+ */
107
+ declare function verifyBlobHash(bytes: Uint8Array, sha256Hex: string): boolean;
108
+ /**
109
+ * Fetch a blob by SHA-256 from a list of Blossom servers, returning the first
110
+ * server's bytes whose hash matches. The returned bytes are always re-verified
111
+ * against `sha256Hex` — servers (and gateways) are never trusted.
112
+ *
113
+ * @param servers - Candidate Blossom server base URLs, tried in order
114
+ * @param sha256Hex - The blob's lowercase-hex SHA-256
115
+ * @param fetchBytes - Fetches raw bytes for a URL (`<server>/<sha256>`)
116
+ * @returns The verified blob bytes
117
+ * @throws {@link NappletResolutionError} `blob-unavailable` if no server serves
118
+ * a hash-matching blob
119
+ */
120
+ declare function fetchBlob(servers: readonly string[], sha256Hex: string, fetchBytes: (url: string) => Promise<Uint8Array>): Promise<Uint8Array>;
121
+ /** A fully verified napplet, ready to inject via `iframe.srcdoc`. */
122
+ interface ResolvedNapplet {
123
+ /** Computed `d` identifier (`''` for root/snapshot). */
124
+ dTag: string;
125
+ /** Computed (and verified) aggregate hash — the content address. */
126
+ aggregateHash: string;
127
+ /** Verified file bytes keyed by manifest path. */
128
+ files: Map<string, Uint8Array>;
129
+ /** The verified `/index.html` decoded to text. */
130
+ indexHtml: string;
131
+ /** The parsed manifest. */
132
+ manifest: NappletManifest;
133
+ }
134
+ /** Options for {@link resolveNapplet}. */
135
+ interface ResolveNappletOptions {
136
+ /** The candidate manifest event (resolved from relays by the caller). */
137
+ event: NostrEvent;
138
+ /**
139
+ * Fetch raw blob bytes by SHA-256. Backed by Blossom (or a gateway) by the
140
+ * caller. The bytes are re-verified against the hash here, so the fetcher is
141
+ * untrusted.
142
+ */
143
+ fetchBlob: (sha256Hex: string, servers: readonly string[]) => Promise<Uint8Array>;
144
+ /** Decode blob bytes to text for `indexHtml` (default UTF-8). */
145
+ textDecode?: (bytes: Uint8Array) => string;
146
+ }
147
+ /**
148
+ * Resolve a napplet end-to-end from a candidate manifest event: verify the
149
+ * signature, parse the manifest, verify the NIP-5A aggregate, fetch and verify
150
+ * every blob, then assemble the verified `/index.html`.
151
+ *
152
+ * The returned `(dTag, aggregateHash)` is computed from the verified bytes and
153
+ * is the napplet's identity. Any failure throws a {@link NappletResolutionError}
154
+ * — the caller must fail closed (never render unverified bytes).
155
+ *
156
+ * @param options - {@link ResolveNappletOptions}
157
+ * @returns The {@link ResolvedNapplet}
158
+ * @throws {@link NappletResolutionError} on signature, manifest, aggregate,
159
+ * blob-hash, blob-availability, or missing-index failures
160
+ *
161
+ * @example
162
+ * ```ts
163
+ * import { resolveNapplet } from '@kehto/nip/5d';
164
+ * const napplet = await resolveNapplet({ event, fetchBlob });
165
+ * iframe.srcdoc = napplet.indexHtml; // sandbox="allow-scripts", opaque origin
166
+ * ```
167
+ */
168
+ declare function resolveNapplet(options: ResolveNappletOptions): Promise<ResolvedNapplet>;
169
+
170
+ export { NAPPLET_KINDS, NAPPLET_KIND_NAMED, NAPPLET_KIND_ROOT, NAPPLET_KIND_SNAPSHOT, type NappletManifest, NappletResolutionError, type NappletResolutionErrorCode, type ResolveNappletOptions, type ResolvedNapplet, fetchBlob, isNappletManifestKind, parseNappletManifest, resolveNapplet, verifyBlobHash, verifyManifestSignature };
@@ -0,0 +1,28 @@
1
+ import {
2
+ NAPPLET_KINDS,
3
+ NAPPLET_KIND_NAMED,
4
+ NAPPLET_KIND_ROOT,
5
+ NAPPLET_KIND_SNAPSHOT,
6
+ NappletResolutionError,
7
+ fetchBlob,
8
+ isNappletManifestKind,
9
+ parseNappletManifest,
10
+ resolveNapplet,
11
+ verifyBlobHash,
12
+ verifyManifestSignature
13
+ } from "../chunk-JRSVIT22.js";
14
+ import "../chunk-2J3CSGKL.js";
15
+ export {
16
+ NAPPLET_KINDS,
17
+ NAPPLET_KIND_NAMED,
18
+ NAPPLET_KIND_ROOT,
19
+ NAPPLET_KIND_SNAPSHOT,
20
+ NappletResolutionError,
21
+ fetchBlob,
22
+ isNappletManifestKind,
23
+ parseNappletManifest,
24
+ resolveNapplet,
25
+ verifyBlobHash,
26
+ verifyManifestSignature
27
+ };
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}