@kehto/nip 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/dist/5d/index.d.ts +151 -1
- package/dist/5d/index.js +11 -1
- package/dist/chunk-CDBUCFLP.js +572 -0
- package/dist/chunk-CDBUCFLP.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +17 -7
- package/package.json +10 -10
- package/dist/chunk-JRSVIT22.js +0 -158
- package/dist/chunk-JRSVIT22.js.map +0 -1
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`) |
|
|
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 |
|
|
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`) |
|
|
@@ -20,6 +20,10 @@ any NIP that isn't referenced.
|
|
|
20
20
|
Each subpath has its own README with full API docs and examples (linked above).
|
|
21
21
|
More unique NIPs are added as their own subpath (`@kehto/nip/<n>`) over time.
|
|
22
22
|
|
|
23
|
+
For browser hosts that want repeated NIP-5D loads to reuse verified artifact
|
|
24
|
+
bytes, see the repo guide:
|
|
25
|
+
[Implement a napplet artifact cache](https://kehto.github.io/web/docs/how-tos/implement-napplet-artifact-cache).
|
|
26
|
+
|
|
23
27
|
## Selection criteria
|
|
24
28
|
|
|
25
29
|
A NIP earns a place here only if it is:
|
package/dist/5d/index.d.ts
CHANGED
|
@@ -1,6 +1,150 @@
|
|
|
1
1
|
import { NostrEvent } from 'nostr-tools';
|
|
2
2
|
import { PathEntry } from '../5a/index.js';
|
|
3
3
|
|
|
4
|
+
/** Versioned Cache Storage namespace for verified napplet artifacts. */
|
|
5
|
+
declare const NAPPLET_ARTIFACT_CACHE_NAME = "kehto:napplet-artifacts:v1";
|
|
6
|
+
interface NappletCacheDiagnostic {
|
|
7
|
+
type: 'cache-open-failed' | 'cache-read-failed' | 'cache-write-failed' | 'cache-delete-failed' | 'cache-prune-failed' | 'storage-estimate-unavailable';
|
|
8
|
+
message: string;
|
|
9
|
+
cause?: unknown;
|
|
10
|
+
}
|
|
11
|
+
interface NappletBlobIndexEntry {
|
|
12
|
+
size: number;
|
|
13
|
+
lastAccessed: number;
|
|
14
|
+
refCount: number;
|
|
15
|
+
}
|
|
16
|
+
interface NappletAggregateIndexEntry {
|
|
17
|
+
dTag: string;
|
|
18
|
+
aggregateHash: string;
|
|
19
|
+
key: string;
|
|
20
|
+
blobHashes: string[];
|
|
21
|
+
size: number;
|
|
22
|
+
lastAccessed: number;
|
|
23
|
+
coordinateKey?: string;
|
|
24
|
+
pinned?: boolean;
|
|
25
|
+
}
|
|
26
|
+
interface CachedCoordinate {
|
|
27
|
+
key: string;
|
|
28
|
+
kind: number;
|
|
29
|
+
pubkey: string;
|
|
30
|
+
dTag: string;
|
|
31
|
+
aggregateHash: string;
|
|
32
|
+
lastResolvedAt: number;
|
|
33
|
+
foregroundTtlMs: number;
|
|
34
|
+
backgroundTtlMs: number;
|
|
35
|
+
}
|
|
36
|
+
interface NappletArtifactCacheIndex {
|
|
37
|
+
schemaVersion: number;
|
|
38
|
+
updatedAt: number;
|
|
39
|
+
blobs: Record<string, NappletBlobIndexEntry>;
|
|
40
|
+
aggregates: Record<string, NappletAggregateIndexEntry>;
|
|
41
|
+
coordinates: Record<string, CachedCoordinate>;
|
|
42
|
+
}
|
|
43
|
+
interface WriteVerifiedResolutionInput {
|
|
44
|
+
event: NostrEvent;
|
|
45
|
+
manifest: NappletManifest;
|
|
46
|
+
files: ReadonlyMap<string, Uint8Array>;
|
|
47
|
+
indexHtml: string;
|
|
48
|
+
now?: number;
|
|
49
|
+
}
|
|
50
|
+
interface CoordinateFreshnessOptions {
|
|
51
|
+
now?: number;
|
|
52
|
+
background?: boolean;
|
|
53
|
+
}
|
|
54
|
+
interface NappletArtifactCache {
|
|
55
|
+
readBlob(sha256Hex: string): Promise<Uint8Array | undefined>;
|
|
56
|
+
deleteBlob(sha256Hex: string): Promise<void>;
|
|
57
|
+
writeVerifiedResolution(input: WriteVerifiedResolutionInput): Promise<void>;
|
|
58
|
+
writeCoordinate(input: {
|
|
59
|
+
kind: number;
|
|
60
|
+
pubkey: string;
|
|
61
|
+
dTag: string;
|
|
62
|
+
aggregateHash: string;
|
|
63
|
+
lastResolvedAt?: number;
|
|
64
|
+
foregroundTtlMs?: number;
|
|
65
|
+
backgroundTtlMs?: number;
|
|
66
|
+
}): Promise<void>;
|
|
67
|
+
getCoordinate(input: {
|
|
68
|
+
kind: number;
|
|
69
|
+
pubkey: string;
|
|
70
|
+
dTag: string;
|
|
71
|
+
}, options?: CoordinateFreshnessOptions): Promise<(CachedCoordinate & {
|
|
72
|
+
fresh: boolean;
|
|
73
|
+
}) | undefined>;
|
|
74
|
+
touchAggregate(dTag: string, aggregateHash: string, now?: number): Promise<void>;
|
|
75
|
+
markAggregateActive(dTag: string, aggregateHash: string): void;
|
|
76
|
+
releaseAggregateActive(dTag: string, aggregateHash: string): void;
|
|
77
|
+
prune(now?: number): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
interface CacheStorageNappletArtifactCacheOptions {
|
|
80
|
+
cacheStorage?: CacheStorage;
|
|
81
|
+
storage?: StorageManager;
|
|
82
|
+
cacheName?: string;
|
|
83
|
+
baseUrl?: string;
|
|
84
|
+
now?: () => number;
|
|
85
|
+
onDiagnostic?: (diagnostic: NappletCacheDiagnostic) => void;
|
|
86
|
+
unknownBudgetBytes?: number;
|
|
87
|
+
softCeilingBytes?: number;
|
|
88
|
+
hardCeilingBytes?: number;
|
|
89
|
+
}
|
|
90
|
+
interface OpenNappletArtifactCacheOptions extends CacheStorageNappletArtifactCacheOptions {
|
|
91
|
+
requireStorageEstimate?: boolean;
|
|
92
|
+
}
|
|
93
|
+
declare function coordinateKey(kind: number, pubkey: string, dTag: string): string;
|
|
94
|
+
declare function isCoordinateFresh(coordinate: CachedCoordinate, options?: CoordinateFreshnessOptions): boolean;
|
|
95
|
+
declare class CacheStorageNappletArtifactCache implements NappletArtifactCache {
|
|
96
|
+
private readonly cacheStorage;
|
|
97
|
+
private readonly storage?;
|
|
98
|
+
private readonly cacheName;
|
|
99
|
+
private readonly baseUrl;
|
|
100
|
+
private readonly now;
|
|
101
|
+
private readonly activeAggregates;
|
|
102
|
+
private readonly onDiagnostic?;
|
|
103
|
+
private readonly unknownBudgetBytes;
|
|
104
|
+
private readonly softCeilingBytes;
|
|
105
|
+
private readonly hardCeilingBytes;
|
|
106
|
+
private cachePromise?;
|
|
107
|
+
constructor(options?: CacheStorageNappletArtifactCacheOptions);
|
|
108
|
+
readBlob(sha256Hex: string): Promise<Uint8Array | undefined>;
|
|
109
|
+
deleteBlob(sha256Hex: string): Promise<void>;
|
|
110
|
+
writeVerifiedResolution(input: WriteVerifiedResolutionInput): Promise<void>;
|
|
111
|
+
writeCoordinate(input: {
|
|
112
|
+
kind: number;
|
|
113
|
+
pubkey: string;
|
|
114
|
+
dTag: string;
|
|
115
|
+
aggregateHash: string;
|
|
116
|
+
lastResolvedAt?: number;
|
|
117
|
+
foregroundTtlMs?: number;
|
|
118
|
+
backgroundTtlMs?: number;
|
|
119
|
+
}): Promise<void>;
|
|
120
|
+
getCoordinate(input: {
|
|
121
|
+
kind: number;
|
|
122
|
+
pubkey: string;
|
|
123
|
+
dTag: string;
|
|
124
|
+
}, options?: CoordinateFreshnessOptions): Promise<(CachedCoordinate & {
|
|
125
|
+
fresh: boolean;
|
|
126
|
+
}) | undefined>;
|
|
127
|
+
touchAggregate(dTag: string, aggregateHash: string, now?: number): Promise<void>;
|
|
128
|
+
markAggregateActive(dTag: string, aggregateHash: string): void;
|
|
129
|
+
releaseAggregateActive(dTag: string, aggregateHash: string): void;
|
|
130
|
+
prune(now?: number): Promise<void>;
|
|
131
|
+
snapshotIndex(): Promise<NappletArtifactCacheIndex>;
|
|
132
|
+
private writeVerifiedResolutionOnce;
|
|
133
|
+
private open;
|
|
134
|
+
private indexUrl;
|
|
135
|
+
private blobUrl;
|
|
136
|
+
private aggregateUrl;
|
|
137
|
+
private readIndex;
|
|
138
|
+
private writeIndex;
|
|
139
|
+
private rewriteRefCounts;
|
|
140
|
+
private budgetBytes;
|
|
141
|
+
private hardBudgetBytes;
|
|
142
|
+
private originPressureHigh;
|
|
143
|
+
private estimateStorage;
|
|
144
|
+
private diagnostic;
|
|
145
|
+
}
|
|
146
|
+
declare function openNappletArtifactCache(options?: OpenNappletArtifactCacheOptions): Promise<CacheStorageNappletArtifactCache | undefined>;
|
|
147
|
+
|
|
4
148
|
/**
|
|
5
149
|
* `@kehto/nip/5d` — NIP-5D napplet manifest resolution.
|
|
6
150
|
*
|
|
@@ -143,6 +287,12 @@ interface ResolveNappletOptions {
|
|
|
143
287
|
fetchBlob: (sha256Hex: string, servers: readonly string[]) => Promise<Uint8Array>;
|
|
144
288
|
/** Decode blob bytes to text for `indexHtml` (default UTF-8). */
|
|
145
289
|
textDecode?: (bytes: Uint8Array) => string;
|
|
290
|
+
/**
|
|
291
|
+
* Optional verified artifact cache. Cache hits are still re-verified against
|
|
292
|
+
* the manifest hash before use; cache writes happen only after the signature,
|
|
293
|
+
* aggregate, and every blob hash have been verified.
|
|
294
|
+
*/
|
|
295
|
+
cache?: NappletArtifactCache;
|
|
146
296
|
}
|
|
147
297
|
/**
|
|
148
298
|
* Resolve a napplet end-to-end from a candidate manifest event: verify the
|
|
@@ -167,4 +317,4 @@ interface ResolveNappletOptions {
|
|
|
167
317
|
*/
|
|
168
318
|
declare function resolveNapplet(options: ResolveNappletOptions): Promise<ResolvedNapplet>;
|
|
169
319
|
|
|
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 };
|
|
320
|
+
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 };
|
package/dist/5d/index.js
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CacheStorageNappletArtifactCache,
|
|
3
|
+
NAPPLET_ARTIFACT_CACHE_NAME,
|
|
2
4
|
NAPPLET_KINDS,
|
|
3
5
|
NAPPLET_KIND_NAMED,
|
|
4
6
|
NAPPLET_KIND_ROOT,
|
|
5
7
|
NAPPLET_KIND_SNAPSHOT,
|
|
6
8
|
NappletResolutionError,
|
|
9
|
+
coordinateKey,
|
|
7
10
|
fetchBlob,
|
|
11
|
+
isCoordinateFresh,
|
|
8
12
|
isNappletManifestKind,
|
|
13
|
+
openNappletArtifactCache,
|
|
9
14
|
parseNappletManifest,
|
|
10
15
|
resolveNapplet,
|
|
11
16
|
verifyBlobHash,
|
|
12
17
|
verifyManifestSignature
|
|
13
|
-
} from "../chunk-
|
|
18
|
+
} from "../chunk-CDBUCFLP.js";
|
|
14
19
|
import "../chunk-2J3CSGKL.js";
|
|
15
20
|
export {
|
|
21
|
+
CacheStorageNappletArtifactCache,
|
|
22
|
+
NAPPLET_ARTIFACT_CACHE_NAME,
|
|
16
23
|
NAPPLET_KINDS,
|
|
17
24
|
NAPPLET_KIND_NAMED,
|
|
18
25
|
NAPPLET_KIND_ROOT,
|
|
19
26
|
NAPPLET_KIND_SNAPSHOT,
|
|
20
27
|
NappletResolutionError,
|
|
28
|
+
coordinateKey,
|
|
21
29
|
fetchBlob,
|
|
30
|
+
isCoordinateFresh,
|
|
22
31
|
isNappletManifestKind,
|
|
32
|
+
openNappletArtifactCache,
|
|
23
33
|
parseNappletManifest,
|
|
24
34
|
resolveNapplet,
|
|
25
35
|
verifyBlobHash,
|
|
@@ -0,0 +1,572 @@
|
|
|
1
|
+
import {
|
|
2
|
+
aggregateTagValue,
|
|
3
|
+
computeAggregateHash,
|
|
4
|
+
pathEntriesFromTags
|
|
5
|
+
} from "./chunk-2J3CSGKL.js";
|
|
6
|
+
|
|
7
|
+
// src/5d/index.ts
|
|
8
|
+
import { verifyEvent } from "nostr-tools/pure";
|
|
9
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
10
|
+
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
11
|
+
|
|
12
|
+
// src/5d/artifact-cache.ts
|
|
13
|
+
var MIB = 1024 * 1024;
|
|
14
|
+
var NAPPLET_ARTIFACT_CACHE_NAME = "kehto:napplet-artifacts:v1";
|
|
15
|
+
var SCHEMA_VERSION = 1;
|
|
16
|
+
var DEFAULT_BASE_URL = "https://kehto.invalid";
|
|
17
|
+
var DEFAULT_UNKNOWN_BUDGET_BYTES = 32 * MIB;
|
|
18
|
+
var DEFAULT_SOFT_CEILING_BYTES = 128 * MIB;
|
|
19
|
+
var DEFAULT_HARD_CEILING_BYTES = 256 * MIB;
|
|
20
|
+
var DEFAULT_FOREGROUND_TTL_MS = 15 * 60 * 1e3;
|
|
21
|
+
var DEFAULT_BACKGROUND_TTL_MS = 60 * 60 * 1e3;
|
|
22
|
+
function emptyIndex(now) {
|
|
23
|
+
return {
|
|
24
|
+
schemaVersion: SCHEMA_VERSION,
|
|
25
|
+
updatedAt: now,
|
|
26
|
+
blobs: {},
|
|
27
|
+
aggregates: {},
|
|
28
|
+
coordinates: {}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function aggregateKey(dTag, aggregateHash) {
|
|
32
|
+
return `${encodeURIComponent(dTag)}:${aggregateHash}`;
|
|
33
|
+
}
|
|
34
|
+
function coordinateKey(kind, pubkey, dTag) {
|
|
35
|
+
return `${kind}:${pubkey}:${encodeURIComponent(dTag)}`;
|
|
36
|
+
}
|
|
37
|
+
function isCoordinateFresh(coordinate, options = {}) {
|
|
38
|
+
const now = options.now ?? Date.now();
|
|
39
|
+
const ttl = options.background ? coordinate.backgroundTtlMs : coordinate.foregroundTtlMs;
|
|
40
|
+
return ttl === Number.POSITIVE_INFINITY || now - coordinate.lastResolvedAt <= ttl;
|
|
41
|
+
}
|
|
42
|
+
function byteLength(bytes) {
|
|
43
|
+
return bytes.byteLength;
|
|
44
|
+
}
|
|
45
|
+
function encodeKeyPart(value) {
|
|
46
|
+
return encodeURIComponent(value).replaceAll("%2F", "%252F");
|
|
47
|
+
}
|
|
48
|
+
function cloneBytes(bytes) {
|
|
49
|
+
return new Uint8Array(bytes);
|
|
50
|
+
}
|
|
51
|
+
function responseBody(bytes) {
|
|
52
|
+
return cloneBytes(bytes).buffer;
|
|
53
|
+
}
|
|
54
|
+
function normalizeBaseUrl(baseUrl) {
|
|
55
|
+
return baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
56
|
+
}
|
|
57
|
+
function resolveBaseUrl(baseUrl) {
|
|
58
|
+
if (baseUrl) return normalizeBaseUrl(baseUrl);
|
|
59
|
+
const maybeLocation = globalThis;
|
|
60
|
+
if (maybeLocation.location?.origin) return normalizeBaseUrl(maybeLocation.location.origin);
|
|
61
|
+
return normalizeBaseUrl(DEFAULT_BASE_URL);
|
|
62
|
+
}
|
|
63
|
+
async function responseBytes(response) {
|
|
64
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
65
|
+
}
|
|
66
|
+
async function responseJson(response) {
|
|
67
|
+
return response.json();
|
|
68
|
+
}
|
|
69
|
+
function refCounts(index) {
|
|
70
|
+
const counts = {};
|
|
71
|
+
for (const aggregate of Object.values(index.aggregates)) {
|
|
72
|
+
for (const hash of aggregate.blobHashes) counts[hash] = (counts[hash] ?? 0) + 1;
|
|
73
|
+
}
|
|
74
|
+
return counts;
|
|
75
|
+
}
|
|
76
|
+
function cachedSize(index) {
|
|
77
|
+
const blobBytes = Object.values(index.blobs).reduce((sum, entry) => sum + entry.size, 0);
|
|
78
|
+
const aggregateBytes = Object.values(index.aggregates).reduce((sum, entry) => sum + entry.size, 0);
|
|
79
|
+
return blobBytes + aggregateBytes;
|
|
80
|
+
}
|
|
81
|
+
var CacheStorageNappletArtifactCache = class {
|
|
82
|
+
cacheStorage;
|
|
83
|
+
storage;
|
|
84
|
+
cacheName;
|
|
85
|
+
baseUrl;
|
|
86
|
+
now;
|
|
87
|
+
activeAggregates = /* @__PURE__ */ new Set();
|
|
88
|
+
onDiagnostic;
|
|
89
|
+
unknownBudgetBytes;
|
|
90
|
+
softCeilingBytes;
|
|
91
|
+
hardCeilingBytes;
|
|
92
|
+
cachePromise;
|
|
93
|
+
constructor(options = {}) {
|
|
94
|
+
const maybeCacheStorage = options.cacheStorage ?? globalThis.caches;
|
|
95
|
+
if (!maybeCacheStorage) {
|
|
96
|
+
throw new Error("Cache Storage is unavailable");
|
|
97
|
+
}
|
|
98
|
+
this.cacheStorage = maybeCacheStorage;
|
|
99
|
+
this.storage = options.storage ?? globalThis.navigator?.storage;
|
|
100
|
+
this.cacheName = options.cacheName ?? NAPPLET_ARTIFACT_CACHE_NAME;
|
|
101
|
+
this.baseUrl = resolveBaseUrl(options.baseUrl);
|
|
102
|
+
this.now = options.now ?? Date.now;
|
|
103
|
+
this.onDiagnostic = options.onDiagnostic;
|
|
104
|
+
this.unknownBudgetBytes = options.unknownBudgetBytes ?? DEFAULT_UNKNOWN_BUDGET_BYTES;
|
|
105
|
+
this.softCeilingBytes = options.softCeilingBytes ?? DEFAULT_SOFT_CEILING_BYTES;
|
|
106
|
+
this.hardCeilingBytes = options.hardCeilingBytes ?? DEFAULT_HARD_CEILING_BYTES;
|
|
107
|
+
}
|
|
108
|
+
async readBlob(sha256Hex) {
|
|
109
|
+
try {
|
|
110
|
+
const cache = await this.open();
|
|
111
|
+
const response = await cache.match(this.blobUrl(sha256Hex));
|
|
112
|
+
if (!response) return void 0;
|
|
113
|
+
const bytes = await responseBytes(response);
|
|
114
|
+
const index = await this.readIndex(cache);
|
|
115
|
+
const entry = index.blobs[sha256Hex];
|
|
116
|
+
if (entry) {
|
|
117
|
+
entry.lastAccessed = this.now();
|
|
118
|
+
index.updatedAt = entry.lastAccessed;
|
|
119
|
+
await this.writeIndex(cache, index);
|
|
120
|
+
}
|
|
121
|
+
return bytes;
|
|
122
|
+
} catch (cause) {
|
|
123
|
+
this.diagnostic({ type: "cache-read-failed", message: `failed to read blob ${sha256Hex}`, cause });
|
|
124
|
+
return void 0;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async deleteBlob(sha256Hex) {
|
|
128
|
+
try {
|
|
129
|
+
const cache = await this.open();
|
|
130
|
+
await cache.delete(this.blobUrl(sha256Hex));
|
|
131
|
+
const index = await this.readIndex(cache);
|
|
132
|
+
delete index.blobs[sha256Hex];
|
|
133
|
+
index.updatedAt = this.now();
|
|
134
|
+
await this.writeIndex(cache, index);
|
|
135
|
+
} catch (cause) {
|
|
136
|
+
this.diagnostic({ type: "cache-delete-failed", message: `failed to delete blob ${sha256Hex}`, cause });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async writeVerifiedResolution(input) {
|
|
140
|
+
try {
|
|
141
|
+
await this.writeVerifiedResolutionOnce(input);
|
|
142
|
+
} catch (firstCause) {
|
|
143
|
+
try {
|
|
144
|
+
await this.prune(input.now ?? this.now());
|
|
145
|
+
await this.writeVerifiedResolutionOnce(input);
|
|
146
|
+
} catch (cause) {
|
|
147
|
+
this.diagnostic({
|
|
148
|
+
type: "cache-write-failed",
|
|
149
|
+
message: "failed to write verified napplet artifacts after prune retry",
|
|
150
|
+
cause: cause ?? firstCause
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
async writeCoordinate(input) {
|
|
156
|
+
const cache = await this.open();
|
|
157
|
+
const now = this.now();
|
|
158
|
+
const index = await this.readIndex(cache);
|
|
159
|
+
const key = coordinateKey(input.kind, input.pubkey, input.dTag);
|
|
160
|
+
index.coordinates[key] = {
|
|
161
|
+
key,
|
|
162
|
+
kind: input.kind,
|
|
163
|
+
pubkey: input.pubkey,
|
|
164
|
+
dTag: input.dTag,
|
|
165
|
+
aggregateHash: input.aggregateHash,
|
|
166
|
+
lastResolvedAt: input.lastResolvedAt ?? now,
|
|
167
|
+
foregroundTtlMs: input.foregroundTtlMs ?? DEFAULT_FOREGROUND_TTL_MS,
|
|
168
|
+
backgroundTtlMs: input.backgroundTtlMs ?? DEFAULT_BACKGROUND_TTL_MS
|
|
169
|
+
};
|
|
170
|
+
index.updatedAt = now;
|
|
171
|
+
await this.writeIndex(cache, index);
|
|
172
|
+
}
|
|
173
|
+
async getCoordinate(input, options = {}) {
|
|
174
|
+
const cache = await this.open();
|
|
175
|
+
const index = await this.readIndex(cache);
|
|
176
|
+
const coordinate = index.coordinates[coordinateKey(input.kind, input.pubkey, input.dTag)];
|
|
177
|
+
if (!coordinate) return void 0;
|
|
178
|
+
return { ...coordinate, fresh: isCoordinateFresh(coordinate, options) };
|
|
179
|
+
}
|
|
180
|
+
async touchAggregate(dTag, aggregateHash, now = this.now()) {
|
|
181
|
+
const cache = await this.open();
|
|
182
|
+
const index = await this.readIndex(cache);
|
|
183
|
+
const key = aggregateKey(dTag, aggregateHash);
|
|
184
|
+
const aggregate = index.aggregates[key];
|
|
185
|
+
if (!aggregate) return;
|
|
186
|
+
aggregate.lastAccessed = now;
|
|
187
|
+
for (const hash of aggregate.blobHashes) {
|
|
188
|
+
if (index.blobs[hash]) index.blobs[hash].lastAccessed = now;
|
|
189
|
+
}
|
|
190
|
+
index.updatedAt = now;
|
|
191
|
+
await this.writeIndex(cache, index);
|
|
192
|
+
}
|
|
193
|
+
markAggregateActive(dTag, aggregateHash) {
|
|
194
|
+
this.activeAggregates.add(aggregateKey(dTag, aggregateHash));
|
|
195
|
+
}
|
|
196
|
+
releaseAggregateActive(dTag, aggregateHash) {
|
|
197
|
+
this.activeAggregates.delete(aggregateKey(dTag, aggregateHash));
|
|
198
|
+
}
|
|
199
|
+
async prune(now = this.now()) {
|
|
200
|
+
try {
|
|
201
|
+
const cache = await this.open();
|
|
202
|
+
const index = await this.readIndex(cache);
|
|
203
|
+
let size = cachedSize(index);
|
|
204
|
+
const budget = await this.budgetBytes();
|
|
205
|
+
const hardBudget = await this.hardBudgetBytes();
|
|
206
|
+
const shouldPrune = size > budget || await this.originPressureHigh();
|
|
207
|
+
if (!shouldPrune) return;
|
|
208
|
+
this.rewriteRefCounts(index);
|
|
209
|
+
for (const [hash, blob] of Object.entries(index.blobs)) {
|
|
210
|
+
if (size <= budget) break;
|
|
211
|
+
if (blob.refCount > 0) continue;
|
|
212
|
+
await cache.delete(this.blobUrl(hash));
|
|
213
|
+
delete index.blobs[hash];
|
|
214
|
+
size -= blob.size;
|
|
215
|
+
}
|
|
216
|
+
const currentAggregateHashes = new Set(
|
|
217
|
+
Object.values(index.coordinates).map((coordinate) => coordinate.aggregateHash)
|
|
218
|
+
);
|
|
219
|
+
const aggregates = Object.values(index.aggregates).filter((entry) => !this.activeAggregates.has(entry.key)).sort((a, b) => {
|
|
220
|
+
const currentA = currentAggregateHashes.has(a.aggregateHash) ? 1 : 0;
|
|
221
|
+
const currentB = currentAggregateHashes.has(b.aggregateHash) ? 1 : 0;
|
|
222
|
+
if (currentA !== currentB) return currentA - currentB;
|
|
223
|
+
return a.lastAccessed - b.lastAccessed;
|
|
224
|
+
});
|
|
225
|
+
for (const aggregate of aggregates) {
|
|
226
|
+
if (size <= budget) break;
|
|
227
|
+
await cache.delete(this.aggregateUrl(aggregate.key));
|
|
228
|
+
delete index.aggregates[aggregate.key];
|
|
229
|
+
size -= aggregate.size;
|
|
230
|
+
this.rewriteRefCounts(index);
|
|
231
|
+
}
|
|
232
|
+
if (size > hardBudget) {
|
|
233
|
+
const active = Object.values(index.aggregates).filter((entry) => this.activeAggregates.has(entry.key)).sort((a, b) => a.lastAccessed - b.lastAccessed);
|
|
234
|
+
for (const aggregate of active) {
|
|
235
|
+
if (size <= hardBudget) break;
|
|
236
|
+
await cache.delete(this.aggregateUrl(aggregate.key));
|
|
237
|
+
delete index.aggregates[aggregate.key];
|
|
238
|
+
size -= aggregate.size;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
this.rewriteRefCounts(index);
|
|
242
|
+
for (const [hash, blob] of Object.entries(index.blobs)) {
|
|
243
|
+
if (blob.refCount === 0) {
|
|
244
|
+
await cache.delete(this.blobUrl(hash));
|
|
245
|
+
delete index.blobs[hash];
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
index.updatedAt = now;
|
|
249
|
+
await this.writeIndex(cache, index);
|
|
250
|
+
} catch (cause) {
|
|
251
|
+
this.diagnostic({ type: "cache-prune-failed", message: "failed to prune napplet artifact cache", cause });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
async snapshotIndex() {
|
|
255
|
+
return this.readIndex(await this.open());
|
|
256
|
+
}
|
|
257
|
+
async writeVerifiedResolutionOnce(input) {
|
|
258
|
+
const cache = await this.open();
|
|
259
|
+
const now = input.now ?? this.now();
|
|
260
|
+
const index = await this.readIndex(cache);
|
|
261
|
+
const blobHashes = [];
|
|
262
|
+
for (const pathEntry of input.manifest.paths) {
|
|
263
|
+
const bytes = input.files.get(pathEntry.path);
|
|
264
|
+
if (!bytes) continue;
|
|
265
|
+
blobHashes.push(pathEntry.sha256);
|
|
266
|
+
await cache.put(this.blobUrl(pathEntry.sha256), new Response(responseBody(bytes)));
|
|
267
|
+
index.blobs[pathEntry.sha256] = {
|
|
268
|
+
size: byteLength(bytes),
|
|
269
|
+
lastAccessed: now,
|
|
270
|
+
refCount: index.blobs[pathEntry.sha256]?.refCount ?? 0
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
const key = aggregateKey(input.manifest.dTag, input.manifest.aggregateHash);
|
|
274
|
+
const record = {
|
|
275
|
+
schemaVersion: SCHEMA_VERSION,
|
|
276
|
+
dTag: input.manifest.dTag,
|
|
277
|
+
aggregateHash: input.manifest.aggregateHash,
|
|
278
|
+
manifest: {
|
|
279
|
+
kind: input.manifest.kind,
|
|
280
|
+
pubkey: input.manifest.pubkey,
|
|
281
|
+
dTag: input.manifest.dTag,
|
|
282
|
+
paths: input.manifest.paths.map((entry) => ({ path: entry.path, sha256: entry.sha256 })),
|
|
283
|
+
requires: [...input.manifest.requires],
|
|
284
|
+
...input.manifest.title === void 0 ? {} : { title: input.manifest.title },
|
|
285
|
+
...input.manifest.description === void 0 ? {} : { description: input.manifest.description }
|
|
286
|
+
},
|
|
287
|
+
indexHtml: input.indexHtml,
|
|
288
|
+
blobHashes,
|
|
289
|
+
writtenAt: now
|
|
290
|
+
};
|
|
291
|
+
const aggregateBytes = new TextEncoder().encode(JSON.stringify(record));
|
|
292
|
+
await cache.put(this.aggregateUrl(key), new Response(responseBody(aggregateBytes), {
|
|
293
|
+
headers: { "content-type": "application/json" }
|
|
294
|
+
}));
|
|
295
|
+
const coordKey = coordinateKey(input.manifest.kind, input.manifest.pubkey, input.manifest.dTag);
|
|
296
|
+
index.aggregates[key] = {
|
|
297
|
+
dTag: input.manifest.dTag,
|
|
298
|
+
aggregateHash: input.manifest.aggregateHash,
|
|
299
|
+
key,
|
|
300
|
+
blobHashes,
|
|
301
|
+
size: byteLength(aggregateBytes),
|
|
302
|
+
lastAccessed: now,
|
|
303
|
+
coordinateKey: coordKey
|
|
304
|
+
};
|
|
305
|
+
index.coordinates[coordKey] = {
|
|
306
|
+
key: coordKey,
|
|
307
|
+
kind: input.manifest.kind,
|
|
308
|
+
pubkey: input.manifest.pubkey,
|
|
309
|
+
dTag: input.manifest.dTag,
|
|
310
|
+
aggregateHash: input.manifest.aggregateHash,
|
|
311
|
+
lastResolvedAt: now,
|
|
312
|
+
foregroundTtlMs: DEFAULT_FOREGROUND_TTL_MS,
|
|
313
|
+
backgroundTtlMs: DEFAULT_BACKGROUND_TTL_MS
|
|
314
|
+
};
|
|
315
|
+
this.rewriteRefCounts(index);
|
|
316
|
+
index.updatedAt = now;
|
|
317
|
+
await this.writeIndex(cache, index);
|
|
318
|
+
await this.prune(now);
|
|
319
|
+
}
|
|
320
|
+
async open() {
|
|
321
|
+
this.cachePromise ??= this.cacheStorage.open(this.cacheName);
|
|
322
|
+
return this.cachePromise;
|
|
323
|
+
}
|
|
324
|
+
indexUrl() {
|
|
325
|
+
return new URL("__kehto_napplet_cache/index.json", this.baseUrl).toString();
|
|
326
|
+
}
|
|
327
|
+
blobUrl(hash) {
|
|
328
|
+
return new URL(`__kehto_napplet_cache/blob/${encodeKeyPart(hash)}`, this.baseUrl).toString();
|
|
329
|
+
}
|
|
330
|
+
aggregateUrl(key) {
|
|
331
|
+
return new URL(`__kehto_napplet_cache/aggregate/${encodeKeyPart(key)}.json`, this.baseUrl).toString();
|
|
332
|
+
}
|
|
333
|
+
async readIndex(cache) {
|
|
334
|
+
const response = await cache.match(this.indexUrl());
|
|
335
|
+
if (!response) return emptyIndex(this.now());
|
|
336
|
+
try {
|
|
337
|
+
const index = await responseJson(response);
|
|
338
|
+
if (index.schemaVersion !== SCHEMA_VERSION) return emptyIndex(this.now());
|
|
339
|
+
return index;
|
|
340
|
+
} catch {
|
|
341
|
+
return emptyIndex(this.now());
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
async writeIndex(cache, index) {
|
|
345
|
+
await cache.put(this.indexUrl(), new Response(JSON.stringify(index), {
|
|
346
|
+
headers: { "content-type": "application/json" }
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
rewriteRefCounts(index) {
|
|
350
|
+
const counts = refCounts(index);
|
|
351
|
+
for (const [hash, blob] of Object.entries(index.blobs)) {
|
|
352
|
+
blob.refCount = counts[hash] ?? 0;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
async budgetBytes() {
|
|
356
|
+
const estimate = await this.estimateStorage();
|
|
357
|
+
if (!estimate?.quota) return this.unknownBudgetBytes;
|
|
358
|
+
return Math.min(this.softCeilingBytes, Math.floor(estimate.quota * 0.1));
|
|
359
|
+
}
|
|
360
|
+
async hardBudgetBytes() {
|
|
361
|
+
const estimate = await this.estimateStorage();
|
|
362
|
+
if (!estimate?.quota) return this.unknownBudgetBytes;
|
|
363
|
+
return Math.min(this.hardCeilingBytes, Math.floor(estimate.quota * 0.2));
|
|
364
|
+
}
|
|
365
|
+
async originPressureHigh() {
|
|
366
|
+
const estimate = await this.estimateStorage();
|
|
367
|
+
if (!estimate?.quota || estimate.usage === void 0) return false;
|
|
368
|
+
return estimate.usage / estimate.quota >= 0.8;
|
|
369
|
+
}
|
|
370
|
+
async estimateStorage() {
|
|
371
|
+
if (!this.storage?.estimate) return void 0;
|
|
372
|
+
try {
|
|
373
|
+
return this.storage.estimate();
|
|
374
|
+
} catch (cause) {
|
|
375
|
+
this.diagnostic({
|
|
376
|
+
type: "storage-estimate-unavailable",
|
|
377
|
+
message: "storage estimate failed; using unknown quota cache budget",
|
|
378
|
+
cause
|
|
379
|
+
});
|
|
380
|
+
return void 0;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
diagnostic(diagnostic) {
|
|
384
|
+
this.onDiagnostic?.(diagnostic);
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
async function openNappletArtifactCache(options = {}) {
|
|
388
|
+
const cacheStorage = options.cacheStorage ?? globalThis.caches;
|
|
389
|
+
if (!cacheStorage) return void 0;
|
|
390
|
+
const storage = options.storage ?? globalThis.navigator?.storage;
|
|
391
|
+
if (options.requireStorageEstimate && !storage?.estimate) {
|
|
392
|
+
options.onDiagnostic?.({
|
|
393
|
+
type: "storage-estimate-unavailable",
|
|
394
|
+
message: "storage estimates are unavailable; using network-only loading"
|
|
395
|
+
});
|
|
396
|
+
return void 0;
|
|
397
|
+
}
|
|
398
|
+
try {
|
|
399
|
+
const cache = new CacheStorageNappletArtifactCache({ ...options, cacheStorage, storage });
|
|
400
|
+
await cache.snapshotIndex();
|
|
401
|
+
return cache;
|
|
402
|
+
} catch (cause) {
|
|
403
|
+
options.onDiagnostic?.({
|
|
404
|
+
type: "cache-open-failed",
|
|
405
|
+
message: "Cache Storage napplet artifact cache could not be opened",
|
|
406
|
+
cause
|
|
407
|
+
});
|
|
408
|
+
return void 0;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// 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
|
+
function isNappletManifestKind(kind) {
|
|
422
|
+
return NAPPLET_KINDS.includes(kind);
|
|
423
|
+
}
|
|
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
|
+
if (typeof tag[1] !== "string" || tag[1].length === 0) continue;
|
|
450
|
+
out.push(
|
|
451
|
+
typeof tag[2] === "string" && tag[2].length > 0 ? { slug: tag[1], nap: tag[2] } : { slug: tag[1] }
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
function parseNappletManifest(event) {
|
|
457
|
+
if (!isNappletManifestKind(event.kind)) {
|
|
458
|
+
throw new NappletResolutionError("invalid-manifest", `not a NIP-5D napplet kind: ${event.kind}`);
|
|
459
|
+
}
|
|
460
|
+
const paths = pathEntriesFromTags(event.tags);
|
|
461
|
+
if (paths.length === 0) {
|
|
462
|
+
throw new NappletResolutionError("invalid-manifest", "manifest has no path tags");
|
|
463
|
+
}
|
|
464
|
+
const aggregateHash = aggregateTagValue(event.tags);
|
|
465
|
+
if (!aggregateHash) {
|
|
466
|
+
throw new NappletResolutionError("invalid-manifest", "manifest has no aggregate x tag");
|
|
467
|
+
}
|
|
468
|
+
return {
|
|
469
|
+
kind: event.kind,
|
|
470
|
+
pubkey: event.pubkey,
|
|
471
|
+
dTag: firstTagValue(event.tags, "d") ?? "",
|
|
472
|
+
paths,
|
|
473
|
+
aggregateHash,
|
|
474
|
+
servers: allTagValues(event.tags, "server"),
|
|
475
|
+
requires: allTagValues(event.tags, "requires"),
|
|
476
|
+
archetypes: archetypesFromTags(event.tags),
|
|
477
|
+
title: firstTagValue(event.tags, "title"),
|
|
478
|
+
description: firstTagValue(event.tags, "description"),
|
|
479
|
+
source: firstTagValue(event.tags, "source")
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
function verifyManifestSignature(event) {
|
|
483
|
+
try {
|
|
484
|
+
return verifyEvent(event);
|
|
485
|
+
} catch {
|
|
486
|
+
return false;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
function verifyBlobHash(bytes, sha256Hex) {
|
|
490
|
+
return bytesToHex(sha256(bytes)) === sha256Hex;
|
|
491
|
+
}
|
|
492
|
+
function stripTrailingSlash(url) {
|
|
493
|
+
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
494
|
+
}
|
|
495
|
+
async function fetchBlob(servers, sha256Hex, fetchBytes) {
|
|
496
|
+
for (const server of servers) {
|
|
497
|
+
const url = `${stripTrailingSlash(server)}/${sha256Hex}`;
|
|
498
|
+
try {
|
|
499
|
+
const bytes = await fetchBytes(url);
|
|
500
|
+
if (verifyBlobHash(bytes, sha256Hex)) return bytes;
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
throw new NappletResolutionError("blob-unavailable", `no server served blob ${sha256Hex}`);
|
|
505
|
+
}
|
|
506
|
+
var INDEX_PATHS = ["/index.html", "index.html", "/"];
|
|
507
|
+
function defaultDecode(bytes) {
|
|
508
|
+
return new TextDecoder().decode(bytes);
|
|
509
|
+
}
|
|
510
|
+
async function resolveNapplet(options) {
|
|
511
|
+
const { event, fetchBlob: fetchBlobBytes, textDecode = defaultDecode, cache } = options;
|
|
512
|
+
if (!verifyManifestSignature(event)) {
|
|
513
|
+
throw new NappletResolutionError("invalid-signature", "manifest signature is invalid");
|
|
514
|
+
}
|
|
515
|
+
const manifest = parseNappletManifest(event);
|
|
516
|
+
const recomputed = computeAggregateHash(manifest.paths);
|
|
517
|
+
if (recomputed !== manifest.aggregateHash) {
|
|
518
|
+
throw new NappletResolutionError(
|
|
519
|
+
"aggregate-mismatch",
|
|
520
|
+
`recomputed aggregate ${recomputed} != manifest ${manifest.aggregateHash}`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
const files = /* @__PURE__ */ new Map();
|
|
524
|
+
for (const entry of manifest.paths) {
|
|
525
|
+
let bytes = await cache?.readBlob(entry.sha256);
|
|
526
|
+
if (bytes && !verifyBlobHash(bytes, entry.sha256)) {
|
|
527
|
+
await cache?.deleteBlob(entry.sha256);
|
|
528
|
+
bytes = void 0;
|
|
529
|
+
}
|
|
530
|
+
bytes ??= await fetchBlobBytes(entry.sha256, manifest.servers);
|
|
531
|
+
if (!verifyBlobHash(bytes, entry.sha256)) {
|
|
532
|
+
throw new NappletResolutionError(
|
|
533
|
+
"blob-hash-mismatch",
|
|
534
|
+
`blob for ${entry.path} does not match hash ${entry.sha256}`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
files.set(entry.path, bytes);
|
|
538
|
+
}
|
|
539
|
+
const indexEntry = manifest.paths.find((e) => INDEX_PATHS.includes(e.path));
|
|
540
|
+
if (!indexEntry) {
|
|
541
|
+
throw new NappletResolutionError("missing-index", "manifest has no /index.html entry");
|
|
542
|
+
}
|
|
543
|
+
const indexHtml = textDecode(files.get(indexEntry.path));
|
|
544
|
+
await cache?.writeVerifiedResolution({ event, manifest, files, indexHtml });
|
|
545
|
+
return {
|
|
546
|
+
dTag: manifest.dTag,
|
|
547
|
+
aggregateHash: manifest.aggregateHash,
|
|
548
|
+
files,
|
|
549
|
+
indexHtml,
|
|
550
|
+
manifest
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
export {
|
|
555
|
+
NAPPLET_ARTIFACT_CACHE_NAME,
|
|
556
|
+
coordinateKey,
|
|
557
|
+
isCoordinateFresh,
|
|
558
|
+
CacheStorageNappletArtifactCache,
|
|
559
|
+
openNappletArtifactCache,
|
|
560
|
+
NAPPLET_KIND_SNAPSHOT,
|
|
561
|
+
NAPPLET_KIND_ROOT,
|
|
562
|
+
NAPPLET_KIND_NAMED,
|
|
563
|
+
NAPPLET_KINDS,
|
|
564
|
+
isNappletManifestKind,
|
|
565
|
+
NappletResolutionError,
|
|
566
|
+
parseNappletManifest,
|
|
567
|
+
verifyManifestSignature,
|
|
568
|
+
verifyBlobHash,
|
|
569
|
+
fetchBlob,
|
|
570
|
+
resolveNapplet
|
|
571
|
+
};
|
|
572
|
+
//# sourceMappingURL=chunk-CDBUCFLP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
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\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 * Archetype slugs this napplet fulfills, from `archetype` manifest tags; the\n * optional `nap` is the recommended default wire protocol (the 3rd tag element).\n */\n archetypes: Array<{ slug: string; nap?: 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; nap?: string }> {\n const out: Array<{ slug: string; nap?: string }> = [];\n for (const tag of tags) {\n if (tag[0] !== 'archetype') continue;\n if (typeof tag[1] !== 'string' || tag[1].length === 0) continue;\n out.push(\n typeof tag[2] === 'string' && tag[2].length > 0\n ? { slug: tag[1], nap: tag[2] }\n : { slug: tag[1] },\n );\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 return {\n kind: event.kind,\n pubkey: event.pubkey,\n dTag: firstTagValue(event.tags, 'd') ?? '',\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 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 message: string;\n cause?: unknown;\n}\n\nexport interface NappletBlobIndexEntry {\n size: number;\n lastAccessed: number;\n refCount: number;\n}\n\nexport interface NappletAggregateIndexEntry {\n dTag: string;\n aggregateHash: string;\n key: string;\n blobHashes: string[];\n size: number;\n lastAccessed: number;\n coordinateKey?: string;\n pinned?: boolean;\n}\n\nexport interface CachedCoordinate {\n key: string;\n kind: number;\n pubkey: string;\n dTag: string;\n aggregateHash: string;\n lastResolvedAt: number;\n foregroundTtlMs: number;\n backgroundTtlMs: number;\n}\n\nexport interface NappletArtifactCacheIndex {\n schemaVersion: number;\n updatedAt: number;\n blobs: Record<string, NappletBlobIndexEntry>;\n aggregates: Record<string, NappletAggregateIndexEntry>;\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 event: NostrEvent;\n manifest: NappletManifest;\n files: ReadonlyMap<string, Uint8Array>;\n indexHtml: string;\n now?: number;\n}\n\nexport interface CoordinateFreshnessOptions {\n now?: number;\n background?: boolean;\n}\n\nexport interface NappletArtifactCache {\n readBlob(sha256Hex: string): Promise<Uint8Array | undefined>;\n deleteBlob(sha256Hex: string): Promise<void>;\n writeVerifiedResolution(input: WriteVerifiedResolutionInput): Promise<void>;\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 getCoordinate(input: {\n kind: number;\n pubkey: string;\n dTag: string;\n }, options?: CoordinateFreshnessOptions): Promise<(CachedCoordinate & { fresh: boolean }) | undefined>;\n touchAggregate(dTag: string, aggregateHash: string, now?: number): Promise<void>;\n markAggregateActive(dTag: string, aggregateHash: string): void;\n releaseAggregateActive(dTag: string, aggregateHash: string): void;\n prune(now?: number): Promise<void>;\n}\n\nexport interface CacheStorageNappletArtifactCacheOptions {\n cacheStorage?: CacheStorage;\n storage?: StorageManager;\n cacheName?: string;\n baseUrl?: string;\n now?: () => number;\n onDiagnostic?: (diagnostic: NappletCacheDiagnostic) => void;\n unknownBudgetBytes?: number;\n softCeilingBytes?: number;\n hardCeilingBytes?: number;\n}\n\nexport interface OpenNappletArtifactCacheOptions extends CacheStorageNappletArtifactCacheOptions {\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\nexport function coordinateKey(kind: number, pubkey: string, dTag: string): string {\n return `${kind}:${pubkey}:${encodeURIComponent(dTag)}`;\n}\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\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 = 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 = 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\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 { NAPPLET_KINDS, NAPPLET_KIND_NAMED, NAPPLET_KIND_ROOT, NAPPLET_KIND_SNAPSHOT, NappletManifest, NappletResolutionError, NappletResolutionErrorCode, ResolveNappletOptions, ResolvedNapplet, fetchBlob, isNappletManifestKind, 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, 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
|
@@ -10,30 +10,35 @@ import {
|
|
|
10
10
|
parseList
|
|
11
11
|
} from "./chunk-2XYRP2S6.js";
|
|
12
12
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
selectReadRelays,
|
|
16
|
-
selectWriteRelays
|
|
17
|
-
} from "./chunk-UUMBCHTA.js";
|
|
18
|
-
import {
|
|
13
|
+
CacheStorageNappletArtifactCache,
|
|
14
|
+
NAPPLET_ARTIFACT_CACHE_NAME,
|
|
19
15
|
NAPPLET_KINDS,
|
|
20
16
|
NAPPLET_KIND_NAMED,
|
|
21
17
|
NAPPLET_KIND_ROOT,
|
|
22
18
|
NAPPLET_KIND_SNAPSHOT,
|
|
23
19
|
NappletResolutionError,
|
|
20
|
+
coordinateKey,
|
|
24
21
|
fetchBlob,
|
|
22
|
+
isCoordinateFresh,
|
|
25
23
|
isNappletManifestKind,
|
|
24
|
+
openNappletArtifactCache,
|
|
26
25
|
parseNappletManifest,
|
|
27
26
|
resolveNapplet,
|
|
28
27
|
verifyBlobHash,
|
|
29
28
|
verifyManifestSignature
|
|
30
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-CDBUCFLP.js";
|
|
31
30
|
import {
|
|
32
31
|
aggregateTagValue,
|
|
33
32
|
computeAggregateHash,
|
|
34
33
|
pathEntriesFromTags,
|
|
35
34
|
verifyAggregate
|
|
36
35
|
} from "./chunk-2J3CSGKL.js";
|
|
36
|
+
import {
|
|
37
|
+
createNip65Registry,
|
|
38
|
+
parseNip65RelayList,
|
|
39
|
+
selectReadRelays,
|
|
40
|
+
selectWriteRelays
|
|
41
|
+
} from "./chunk-UUMBCHTA.js";
|
|
37
42
|
import {
|
|
38
43
|
DEFAULT_RELAY_ATTRIBUTE_GROUPS,
|
|
39
44
|
createNip66Aggregator
|
|
@@ -48,12 +53,14 @@ import {
|
|
|
48
53
|
parseHandlerRecommendation
|
|
49
54
|
} from "./chunk-BCTIL523.js";
|
|
50
55
|
export {
|
|
56
|
+
CacheStorageNappletArtifactCache,
|
|
51
57
|
DEFAULT_RELAY_ATTRIBUTE_GROUPS,
|
|
52
58
|
HANDLER_INFORMATION_KIND,
|
|
53
59
|
HANDLER_PLATFORMS,
|
|
54
60
|
HANDLER_RECOMMENDATION_KIND,
|
|
55
61
|
ITEM_TAGS,
|
|
56
62
|
LIST_KINDS,
|
|
63
|
+
NAPPLET_ARTIFACT_CACHE_NAME,
|
|
57
64
|
NAPPLET_KINDS,
|
|
58
65
|
NAPPLET_KIND_NAMED,
|
|
59
66
|
NAPPLET_KIND_ROOT,
|
|
@@ -63,16 +70,19 @@ export {
|
|
|
63
70
|
aggregateTagValue,
|
|
64
71
|
buildHandlerUrl,
|
|
65
72
|
computeAggregateHash,
|
|
73
|
+
coordinateKey,
|
|
66
74
|
createNip65Registry,
|
|
67
75
|
createNip66Aggregator,
|
|
68
76
|
decryptPrivateItems,
|
|
69
77
|
fetchBlob,
|
|
70
78
|
getTagValues,
|
|
71
79
|
handlesKind,
|
|
80
|
+
isCoordinateFresh,
|
|
72
81
|
isListKind,
|
|
73
82
|
isNappletManifestKind,
|
|
74
83
|
isSetKind,
|
|
75
84
|
listKindName,
|
|
85
|
+
openNappletArtifactCache,
|
|
76
86
|
parseHandlerInformation,
|
|
77
87
|
parseHandlerRecommendation,
|
|
78
88
|
parseList,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kehto/nip",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
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",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
|
-
"nostr-tools": ">=2.23.3
|
|
47
|
+
"nostr-tools": ">=2.23.3 <=2.x"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@noble/hashes": "^2.0.0"
|
|
@@ -55,12 +55,6 @@
|
|
|
55
55
|
"typescript": "^5.9.3",
|
|
56
56
|
"vitest": "^4.1.2"
|
|
57
57
|
},
|
|
58
|
-
"scripts": {
|
|
59
|
-
"build": "tsup",
|
|
60
|
-
"type-check": "tsc --noEmit",
|
|
61
|
-
"test:unit": "cd ../.. && vitest run --config vitest.config.ts packages/nip",
|
|
62
|
-
"test": "pnpm test:unit"
|
|
63
|
-
},
|
|
64
58
|
"license": "MIT",
|
|
65
59
|
"repository": {
|
|
66
60
|
"type": "git",
|
|
@@ -85,5 +79,11 @@
|
|
|
85
79
|
"app-handlers",
|
|
86
80
|
"kehto",
|
|
87
81
|
"napplet"
|
|
88
|
-
]
|
|
89
|
-
|
|
82
|
+
],
|
|
83
|
+
"scripts": {
|
|
84
|
+
"build": "tsup",
|
|
85
|
+
"type-check": "tsc --noEmit",
|
|
86
|
+
"test:unit": "cd ../.. && vitest run --config vitest.config.ts packages/nip",
|
|
87
|
+
"test": "pnpm test:unit"
|
|
88
|
+
}
|
|
89
|
+
}
|
package/dist/chunk-JRSVIT22.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
aggregateTagValue,
|
|
3
|
-
computeAggregateHash,
|
|
4
|
-
pathEntriesFromTags
|
|
5
|
-
} from "./chunk-2J3CSGKL.js";
|
|
6
|
-
|
|
7
|
-
// src/5d/index.ts
|
|
8
|
-
import { verifyEvent } from "nostr-tools/pure";
|
|
9
|
-
import { sha256 } from "@noble/hashes/sha2.js";
|
|
10
|
-
import { bytesToHex } from "@noble/hashes/utils.js";
|
|
11
|
-
var NAPPLET_KIND_SNAPSHOT = 5129;
|
|
12
|
-
var NAPPLET_KIND_ROOT = 15129;
|
|
13
|
-
var NAPPLET_KIND_NAMED = 35129;
|
|
14
|
-
var NAPPLET_KINDS = [
|
|
15
|
-
NAPPLET_KIND_SNAPSHOT,
|
|
16
|
-
NAPPLET_KIND_ROOT,
|
|
17
|
-
NAPPLET_KIND_NAMED
|
|
18
|
-
];
|
|
19
|
-
function isNappletManifestKind(kind) {
|
|
20
|
-
return NAPPLET_KINDS.includes(kind);
|
|
21
|
-
}
|
|
22
|
-
var NappletResolutionError = class extends Error {
|
|
23
|
-
code;
|
|
24
|
-
constructor(code, message) {
|
|
25
|
-
super(message);
|
|
26
|
-
this.name = "NappletResolutionError";
|
|
27
|
-
this.code = code;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
function firstTagValue(tags, name) {
|
|
31
|
-
for (const tag of tags) {
|
|
32
|
-
if (tag[0] === name && typeof tag[1] === "string" && tag[1].length > 0) return tag[1];
|
|
33
|
-
}
|
|
34
|
-
return void 0;
|
|
35
|
-
}
|
|
36
|
-
function allTagValues(tags, name) {
|
|
37
|
-
const out = [];
|
|
38
|
-
for (const tag of tags) {
|
|
39
|
-
if (tag[0] === name && typeof tag[1] === "string" && tag[1].length > 0) out.push(tag[1]);
|
|
40
|
-
}
|
|
41
|
-
return out;
|
|
42
|
-
}
|
|
43
|
-
function archetypesFromTags(tags) {
|
|
44
|
-
const out = [];
|
|
45
|
-
for (const tag of tags) {
|
|
46
|
-
if (tag[0] !== "archetype") continue;
|
|
47
|
-
if (typeof tag[1] !== "string" || tag[1].length === 0) continue;
|
|
48
|
-
out.push(
|
|
49
|
-
typeof tag[2] === "string" && tag[2].length > 0 ? { slug: tag[1], nap: tag[2] } : { slug: tag[1] }
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
return out;
|
|
53
|
-
}
|
|
54
|
-
function parseNappletManifest(event) {
|
|
55
|
-
if (!isNappletManifestKind(event.kind)) {
|
|
56
|
-
throw new NappletResolutionError("invalid-manifest", `not a NIP-5D napplet kind: ${event.kind}`);
|
|
57
|
-
}
|
|
58
|
-
const paths = pathEntriesFromTags(event.tags);
|
|
59
|
-
if (paths.length === 0) {
|
|
60
|
-
throw new NappletResolutionError("invalid-manifest", "manifest has no path tags");
|
|
61
|
-
}
|
|
62
|
-
const aggregateHash = aggregateTagValue(event.tags);
|
|
63
|
-
if (!aggregateHash) {
|
|
64
|
-
throw new NappletResolutionError("invalid-manifest", "manifest has no aggregate x tag");
|
|
65
|
-
}
|
|
66
|
-
return {
|
|
67
|
-
kind: event.kind,
|
|
68
|
-
pubkey: event.pubkey,
|
|
69
|
-
dTag: firstTagValue(event.tags, "d") ?? "",
|
|
70
|
-
paths,
|
|
71
|
-
aggregateHash,
|
|
72
|
-
servers: allTagValues(event.tags, "server"),
|
|
73
|
-
requires: allTagValues(event.tags, "requires"),
|
|
74
|
-
archetypes: archetypesFromTags(event.tags),
|
|
75
|
-
title: firstTagValue(event.tags, "title"),
|
|
76
|
-
description: firstTagValue(event.tags, "description"),
|
|
77
|
-
source: firstTagValue(event.tags, "source")
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
function verifyManifestSignature(event) {
|
|
81
|
-
try {
|
|
82
|
-
return verifyEvent(event);
|
|
83
|
-
} catch {
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
function verifyBlobHash(bytes, sha256Hex) {
|
|
88
|
-
return bytesToHex(sha256(bytes)) === sha256Hex;
|
|
89
|
-
}
|
|
90
|
-
function stripTrailingSlash(url) {
|
|
91
|
-
return url.endsWith("/") ? url.slice(0, -1) : url;
|
|
92
|
-
}
|
|
93
|
-
async function fetchBlob(servers, sha256Hex, fetchBytes) {
|
|
94
|
-
for (const server of servers) {
|
|
95
|
-
const url = `${stripTrailingSlash(server)}/${sha256Hex}`;
|
|
96
|
-
try {
|
|
97
|
-
const bytes = await fetchBytes(url);
|
|
98
|
-
if (verifyBlobHash(bytes, sha256Hex)) return bytes;
|
|
99
|
-
} catch {
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
throw new NappletResolutionError("blob-unavailable", `no server served blob ${sha256Hex}`);
|
|
103
|
-
}
|
|
104
|
-
var INDEX_PATHS = ["/index.html", "index.html", "/"];
|
|
105
|
-
function defaultDecode(bytes) {
|
|
106
|
-
return new TextDecoder().decode(bytes);
|
|
107
|
-
}
|
|
108
|
-
async function resolveNapplet(options) {
|
|
109
|
-
const { event, fetchBlob: fetchBlobBytes, textDecode = defaultDecode } = options;
|
|
110
|
-
if (!verifyManifestSignature(event)) {
|
|
111
|
-
throw new NappletResolutionError("invalid-signature", "manifest signature is invalid");
|
|
112
|
-
}
|
|
113
|
-
const manifest = parseNappletManifest(event);
|
|
114
|
-
const recomputed = computeAggregateHash(manifest.paths);
|
|
115
|
-
if (recomputed !== manifest.aggregateHash) {
|
|
116
|
-
throw new NappletResolutionError(
|
|
117
|
-
"aggregate-mismatch",
|
|
118
|
-
`recomputed aggregate ${recomputed} != manifest ${manifest.aggregateHash}`
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
const files = /* @__PURE__ */ new Map();
|
|
122
|
-
for (const entry of manifest.paths) {
|
|
123
|
-
const bytes = await fetchBlobBytes(entry.sha256, manifest.servers);
|
|
124
|
-
if (!verifyBlobHash(bytes, entry.sha256)) {
|
|
125
|
-
throw new NappletResolutionError(
|
|
126
|
-
"blob-hash-mismatch",
|
|
127
|
-
`blob for ${entry.path} does not match hash ${entry.sha256}`
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
files.set(entry.path, bytes);
|
|
131
|
-
}
|
|
132
|
-
const indexEntry = manifest.paths.find((e) => INDEX_PATHS.includes(e.path));
|
|
133
|
-
if (!indexEntry) {
|
|
134
|
-
throw new NappletResolutionError("missing-index", "manifest has no /index.html entry");
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
dTag: manifest.dTag,
|
|
138
|
-
aggregateHash: manifest.aggregateHash,
|
|
139
|
-
files,
|
|
140
|
-
indexHtml: textDecode(files.get(indexEntry.path)),
|
|
141
|
-
manifest
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export {
|
|
146
|
-
NAPPLET_KIND_SNAPSHOT,
|
|
147
|
-
NAPPLET_KIND_ROOT,
|
|
148
|
-
NAPPLET_KIND_NAMED,
|
|
149
|
-
NAPPLET_KINDS,
|
|
150
|
-
isNappletManifestKind,
|
|
151
|
-
NappletResolutionError,
|
|
152
|
-
parseNappletManifest,
|
|
153
|
-
verifyManifestSignature,
|
|
154
|
-
verifyBlobHash,
|
|
155
|
-
fetchBlob,
|
|
156
|
-
resolveNapplet
|
|
157
|
-
};
|
|
158
|
-
//# sourceMappingURL=chunk-JRSVIT22.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/5d/index.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';\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\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 * Archetype slugs this napplet fulfills, from `archetype` manifest tags; the\n * optional `nap` is the recommended default wire protocol (the 3rd tag element).\n */\n archetypes: Array<{ slug: string; nap?: 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; nap?: string }> {\n const out: Array<{ slug: string; nap?: string }> = [];\n for (const tag of tags) {\n if (tag[0] !== 'archetype') continue;\n if (typeof tag[1] !== 'string' || tag[1].length === 0) continue;\n out.push(\n typeof tag[2] === 'string' && tag[2].length > 0\n ? { slug: tag[1], nap: tag[2] }\n : { slug: tag[1] },\n );\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 return {\n kind: event.kind,\n pubkey: event.pubkey,\n dTag: firstTagValue(event.tags, 'd') ?? '',\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\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 } = 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 const 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 return {\n dTag: manifest.dTag,\n aggregateHash: manifest.aggregateHash,\n files,\n indexHtml: textDecode(files.get(indexEntry.path)!),\n manifest,\n };\n}\n"],"mappings":"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","names":[]}
|