@qntx/nostr 0.0.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/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/builder-BphJa-Y-.d.mts +67 -0
- package/dist/builder-q0VsvnYE.mjs +158 -0
- package/dist/builder-q0VsvnYE.mjs.map +1 -0
- package/dist/client-Bd8B1mYm.mjs +835 -0
- package/dist/client-Bd8B1mYm.mjs.map +1 -0
- package/dist/client.d.mts +2 -0
- package/dist/client.mjs +2 -0
- package/dist/core.d.mts +8 -0
- package/dist/core.mjs +9 -0
- package/dist/error-B49rbDzT.d.mts +7 -0
- package/dist/error-CwBxle6N.mjs +13 -0
- package/dist/error-CwBxle6N.mjs.map +1 -0
- package/dist/error-DZEs5OuZ.mjs +24 -0
- package/dist/error-DZEs5OuZ.mjs.map +1 -0
- package/dist/error-WEFS-R-4.d.mts +20 -0
- package/dist/event-B5D2jxt_.d.mts +108 -0
- package/dist/event-CzoQEFMF.mjs +188 -0
- package/dist/event-CzoQEFMF.mjs.map +1 -0
- package/dist/filter-BxwjEkZp.mjs +113 -0
- package/dist/filter-BxwjEkZp.mjs.map +1 -0
- package/dist/filter-DUEKP3q1.d.mts +40 -0
- package/dist/gossip-3xkRRCm_.mjs +220 -0
- package/dist/gossip-3xkRRCm_.mjs.map +1 -0
- package/dist/gossip-C5RVB1UB.d.mts +71 -0
- package/dist/gossip.d.mts +3 -0
- package/dist/gossip.mjs +2 -0
- package/dist/http-6vevbBlu.d.mts +22 -0
- package/dist/http-D2rhokZo.mjs +24 -0
- package/dist/http-D2rhokZo.mjs.map +1 -0
- package/dist/index-BYMnhRIC.d.mts +161 -0
- package/dist/index-C0C4e-Gn.d.mts +55 -0
- package/dist/index-CaxrMD1A.d.mts +1 -0
- package/dist/index-DaRyjo7O.d.mts +77 -0
- package/dist/index-DgOugrpF.d.mts +275 -0
- package/dist/index-EwL6fHLo.d.mts +267 -0
- package/dist/index-k2YhCgsh.d.mts +57 -0
- package/dist/index.d.mts +22 -0
- package/dist/index.mjs +23 -0
- package/dist/key-BiGxGLhg.d.mts +37 -0
- package/dist/key-Dn-aX4x4.mjs +130 -0
- package/dist/key-Dn-aX4x4.mjs.map +1 -0
- package/dist/kind-BLl4cLOc.mjs +59 -0
- package/dist/kind-BLl4cLOc.mjs.map +1 -0
- package/dist/loaders-BNQszDAn.mjs +767 -0
- package/dist/loaders-BNQszDAn.mjs.map +1 -0
- package/dist/loaders.d.mts +3 -0
- package/dist/loaders.mjs +2 -0
- package/dist/memory-BCmhZPek.mjs +461 -0
- package/dist/memory-BCmhZPek.mjs.map +1 -0
- package/dist/message-C_Uj-Y-c.mjs +179 -0
- package/dist/message-C_Uj-Y-c.mjs.map +1 -0
- package/dist/message-DJC-Jz6T.d.mts +32 -0
- package/dist/nip17-BWq-Kfq5.d.mts +38 -0
- package/dist/nip19-D7905oIv.d.mts +63 -0
- package/dist/nip46-C_DoxiS_.d.mts +51 -0
- package/dist/nip59-DljW_sYh.d.mts +76 -0
- package/dist/nip65-Di5hZRuV.d.mts +21 -0
- package/dist/nip77-ByxlgoQO.d.mts +55 -0
- package/dist/nips/blossom.d.mts +93 -0
- package/dist/nips/blossom.mjs +325 -0
- package/dist/nips/blossom.mjs.map +1 -0
- package/dist/nips/nip04.d.mts +8 -0
- package/dist/nips/nip04.mjs +41 -0
- package/dist/nips/nip04.mjs.map +1 -0
- package/dist/nips/nip05.d.mts +89 -0
- package/dist/nips/nip05.mjs +147 -0
- package/dist/nips/nip05.mjs.map +1 -0
- package/dist/nips/nip10.d.mts +43 -0
- package/dist/nips/nip10.mjs +194 -0
- package/dist/nips/nip10.mjs.map +1 -0
- package/dist/nips/nip11.d.mts +43 -0
- package/dist/nips/nip11.mjs +99 -0
- package/dist/nips/nip11.mjs.map +1 -0
- package/dist/nips/nip13.d.mts +23 -0
- package/dist/nips/nip13.mjs +88 -0
- package/dist/nips/nip13.mjs.map +1 -0
- package/dist/nips/nip17.d.mts +2 -0
- package/dist/nips/nip17.mjs +138 -0
- package/dist/nips/nip17.mjs.map +1 -0
- package/dist/nips/nip19.d.mts +2 -0
- package/dist/nips/nip19.mjs +174 -0
- package/dist/nips/nip19.mjs.map +1 -0
- package/dist/nips/nip21.d.mts +17 -0
- package/dist/nips/nip21.mjs +29 -0
- package/dist/nips/nip21.mjs.map +1 -0
- package/dist/nips/nip27.d.mts +42 -0
- package/dist/nips/nip27.mjs +175 -0
- package/dist/nips/nip27.mjs.map +1 -0
- package/dist/nips/nip42.d.mts +12 -0
- package/dist/nips/nip42.mjs +22 -0
- package/dist/nips/nip42.mjs.map +1 -0
- package/dist/nips/nip44.d.mts +12 -0
- package/dist/nips/nip44.mjs +125 -0
- package/dist/nips/nip44.mjs.map +1 -0
- package/dist/nips/nip46.d.mts +2 -0
- package/dist/nips/nip46.mjs +132 -0
- package/dist/nips/nip46.mjs.map +1 -0
- package/dist/nips/nip49.d.mts +12 -0
- package/dist/nips/nip49.mjs +80 -0
- package/dist/nips/nip49.mjs.map +1 -0
- package/dist/nips/nip51.d.mts +88 -0
- package/dist/nips/nip51.mjs +246 -0
- package/dist/nips/nip51.mjs.map +1 -0
- package/dist/nips/nip57.d.mts +43 -0
- package/dist/nips/nip57.mjs +230 -0
- package/dist/nips/nip57.mjs.map +1 -0
- package/dist/nips/nip59.d.mts +2 -0
- package/dist/nips/nip59.mjs +171 -0
- package/dist/nips/nip59.mjs.map +1 -0
- package/dist/nips/nip65.d.mts +2 -0
- package/dist/nips/nip65.mjs +76 -0
- package/dist/nips/nip65.mjs.map +1 -0
- package/dist/nips/nip77.d.mts +2 -0
- package/dist/nips/nip77.mjs +421 -0
- package/dist/nips/nip77.mjs.map +1 -0
- package/dist/nips/nip96.d.mts +31 -0
- package/dist/nips/nip96.mjs +95 -0
- package/dist/nips/nip96.mjs.map +1 -0
- package/dist/nips/nip98.d.mts +28 -0
- package/dist/nips/nip98.mjs +91 -0
- package/dist/nips/nip98.mjs.map +1 -0
- package/dist/nostr_crypto_wasm_bg-hi5aXi5P.mjs +4 -0
- package/dist/nostr_crypto_wasm_bg.wasm +0 -0
- package/dist/pool-C_j1OmBB.mjs +1881 -0
- package/dist/pool-C_j1OmBB.mjs.map +1 -0
- package/dist/pool-DhRi7l-b.d.mts +297 -0
- package/dist/relay.d.mts +4 -0
- package/dist/relay.mjs +2 -0
- package/dist/signer-CZ6K6FUc.mjs +473 -0
- package/dist/signer-CZ6K6FUc.mjs.map +1 -0
- package/dist/signer.d.mts +3 -0
- package/dist/signer.mjs +2 -0
- package/dist/storage-DOy0iLRE.mjs +870 -0
- package/dist/storage-DOy0iLRE.mjs.map +1 -0
- package/dist/storage.d.mts +4 -0
- package/dist/storage.mjs +4 -0
- package/dist/types-BzyADqnw.d.mts +19 -0
- package/dist/types-Dn24TPFs.d.mts +43 -0
- package/dist/util-6tfLQ9JF.mjs +73 -0
- package/dist/util-6tfLQ9JF.mjs.map +1 -0
- package/dist/wasm.d.mts +19 -0
- package/dist/wasm.mjs +237 -0
- package/dist/wasm.mjs.map +1 -0
- package/package.json +204 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { l as utf8Encoder, r as bytesToHex, t as assertHex32 } from "../util-6tfLQ9JF.mjs";
|
|
2
|
+
import { a as NostrError, n as EventValidationError } from "../error-DZEs5OuZ.mjs";
|
|
3
|
+
import { t as Kind } from "../kind-BLl4cLOc.mjs";
|
|
4
|
+
import { t as EventBuilder } from "../builder-q0VsvnYE.mjs";
|
|
5
|
+
import { n as requireGlobalFetch, r as sendManual, t as fetchManual } from "../http-D2rhokZo.mjs";
|
|
6
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
7
|
+
import { base64urlnopad } from "@scure/base";
|
|
8
|
+
//#region src/nips/blossom.ts
|
|
9
|
+
/**
|
|
10
|
+
* Blossom BUD-01/02/04/06 HTTP helpers, NIP-B7 kind 10063 server lists, and URL healing.
|
|
11
|
+
* Auth is kind 24242, not NIP-98; the Authorization value is base64url.
|
|
12
|
+
*
|
|
13
|
+
* @see https://github.com/hzrd149/blossom
|
|
14
|
+
* @see https://github.com/nostr-protocol/nips/blob/master/B7.md
|
|
15
|
+
*/
|
|
16
|
+
const AUTH_VERBS = /* @__PURE__ */ new Set([
|
|
17
|
+
"upload",
|
|
18
|
+
"delete",
|
|
19
|
+
"list",
|
|
20
|
+
"get",
|
|
21
|
+
"media"
|
|
22
|
+
]);
|
|
23
|
+
/** Default BUD-11 expiration: now + 1h. Servers 401 when the tag is missing. */
|
|
24
|
+
const AUTH_EXPIRATION_SECS = 3600;
|
|
25
|
+
var BlossomError = class extends NostrError {
|
|
26
|
+
status;
|
|
27
|
+
constructor(message, options) {
|
|
28
|
+
const { status, ...rest } = options ?? {};
|
|
29
|
+
super(message, rest);
|
|
30
|
+
this.status = status;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
/** Last 64-char hex in the URL path (extension ignored). Null if none or the URL is invalid. */
|
|
34
|
+
function getHashFromURL(url) {
|
|
35
|
+
let path;
|
|
36
|
+
try {
|
|
37
|
+
path = (url instanceof URL ? url : new URL(url)).pathname;
|
|
38
|
+
} catch {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const matches = path.match(/[0-9a-f]{64}/gi);
|
|
42
|
+
if (!matches?.length) return null;
|
|
43
|
+
return matches[matches.length - 1].toLowerCase();
|
|
44
|
+
}
|
|
45
|
+
async function sha256Blob(data) {
|
|
46
|
+
let bytes;
|
|
47
|
+
if (data instanceof Uint8Array) bytes = data;
|
|
48
|
+
else if (data instanceof ArrayBuffer) bytes = new Uint8Array(data);
|
|
49
|
+
else bytes = new Uint8Array(await data.arrayBuffer());
|
|
50
|
+
return bytesToHex(sha256(bytes));
|
|
51
|
+
}
|
|
52
|
+
/** True when `sha256Blob(data)` equals the expected hex (any case). */
|
|
53
|
+
async function verifyBlob(data, sha256Hex) {
|
|
54
|
+
const expected = assertHex32(sha256Hex, "blob sha256");
|
|
55
|
+
return await sha256Blob(data) === expected;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* `Nostr ` + base64url(utf8 JSON), padding stripped.
|
|
59
|
+
* NIP-98 uses standard base64; mixing them is a 401.
|
|
60
|
+
*/
|
|
61
|
+
function encodeAuthorizationHeader(event) {
|
|
62
|
+
return `Nostr ${base64urlnopad.encode(utf8Encoder.encode(JSON.stringify(event)))}`;
|
|
63
|
+
}
|
|
64
|
+
function createAuthTemplate(verb, opts) {
|
|
65
|
+
if (!AUTH_VERBS.has(verb)) throw new BlossomError(`invalid blossom auth verb: ${verb}`);
|
|
66
|
+
const expiration = opts?.expiration ?? Math.floor(Date.now() / 1e3) + AUTH_EXPIRATION_SECS;
|
|
67
|
+
const tags = [["t", verb], ["expiration", String(expiration)]];
|
|
68
|
+
if (opts?.sha256 !== void 0) tags.push(["x", assertHex32(opts.sha256, "blob sha256")]);
|
|
69
|
+
return {
|
|
70
|
+
kind: Kind.BlobsAuth,
|
|
71
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
72
|
+
tags,
|
|
73
|
+
content: opts?.message ?? ""
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function createUploadAuth(sign, file, opts) {
|
|
77
|
+
return sign(createAuthTemplate("upload", {
|
|
78
|
+
sha256: await sha256Blob(file),
|
|
79
|
+
...opts
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
async function upload(server, file, auth, opts) {
|
|
83
|
+
const hash = await sha256Blob(file);
|
|
84
|
+
const headers = {
|
|
85
|
+
Authorization: encodeAuthorizationHeader(auth),
|
|
86
|
+
"X-SHA-256": hash
|
|
87
|
+
};
|
|
88
|
+
if (file.type) headers["Content-Type"] = file.type;
|
|
89
|
+
return requireDescriptorHash(parseBlobDescriptor(await readJson(await blossomRequest(opts?.fetch, blossomUrl(server, "/upload"), {
|
|
90
|
+
method: "PUT",
|
|
91
|
+
headers,
|
|
92
|
+
body: file,
|
|
93
|
+
signal: opts?.signal
|
|
94
|
+
}))), hash);
|
|
95
|
+
}
|
|
96
|
+
async function listBlobs(server, pubkey, auth, opts) {
|
|
97
|
+
const pk = assertHex32(pubkey, "pubkey");
|
|
98
|
+
const headers = {};
|
|
99
|
+
if (auth) headers.Authorization = encodeAuthorizationHeader(auth);
|
|
100
|
+
const res = await blossomRequest(opts?.fetch, blossomUrl(server, `/list/${pk}`), {
|
|
101
|
+
method: "GET",
|
|
102
|
+
headers,
|
|
103
|
+
signal: opts?.signal
|
|
104
|
+
});
|
|
105
|
+
const json = await readJson(res);
|
|
106
|
+
if (!Array.isArray(json)) throw new BlossomError("blossom list response must be a JSON array", { status: res.status });
|
|
107
|
+
return json.map(parseBlobDescriptor);
|
|
108
|
+
}
|
|
109
|
+
async function deleteBlob(server, sha256Hex, auth, opts) {
|
|
110
|
+
const hash = assertHex32(sha256Hex, "blob sha256");
|
|
111
|
+
await blossomRequest(opts?.fetch, blossomUrl(server, `/${hash}`), {
|
|
112
|
+
method: "DELETE",
|
|
113
|
+
headers: { Authorization: encodeAuthorizationHeader(auth) },
|
|
114
|
+
signal: opts?.signal
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
async function mirrorBlob(server, blob, opts) {
|
|
118
|
+
const hash = assertHex32(blob.sha256, "blob sha256");
|
|
119
|
+
const headers = {
|
|
120
|
+
Authorization: encodeAuthorizationHeader(opts.auth),
|
|
121
|
+
"Content-Type": "application/json",
|
|
122
|
+
"X-SHA-256": hash,
|
|
123
|
+
"X-Content-Length": String(blob.size)
|
|
124
|
+
};
|
|
125
|
+
if (blob.type) headers["X-Content-Type"] = blob.type;
|
|
126
|
+
return requireDescriptorHash(parseBlobDescriptor(await readJson(await blossomRequest(opts.fetch, blossomUrl(server, "/mirror"), {
|
|
127
|
+
method: "PUT",
|
|
128
|
+
headers,
|
|
129
|
+
body: JSON.stringify({ url: blob.url }),
|
|
130
|
+
signal: opts.signal
|
|
131
|
+
}))), hash);
|
|
132
|
+
}
|
|
133
|
+
/** BUD-06 preflight. Does not PUT the body. */
|
|
134
|
+
async function checkUpload(server, file, auth, opts) {
|
|
135
|
+
const hash = await sha256Blob(file);
|
|
136
|
+
const headers = {
|
|
137
|
+
Authorization: encodeAuthorizationHeader(auth),
|
|
138
|
+
"X-SHA-256": hash,
|
|
139
|
+
"X-Content-Length": String(file.size)
|
|
140
|
+
};
|
|
141
|
+
if (file.type) headers["X-Content-Type"] = file.type;
|
|
142
|
+
await blossomRequest(opts?.fetch, blossomUrl(server, "/upload"), {
|
|
143
|
+
method: "HEAD",
|
|
144
|
+
headers,
|
|
145
|
+
signal: opts?.signal
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/** Strict HEAD probe. 2xx → true. 404 → false. Other HTTP (including 405) / network → throw BlossomError. AbortError propagates. No GET fallback. */
|
|
149
|
+
async function blobExists(server, sha256Hex, opts) {
|
|
150
|
+
const url = blossomUrl(server, `/${assertHex32(sha256Hex, "blob sha256")}`);
|
|
151
|
+
const res = await blossomFetch(opts?.fetch, url, {
|
|
152
|
+
method: "HEAD",
|
|
153
|
+
signal: opts?.signal
|
|
154
|
+
});
|
|
155
|
+
if (res.status === 404) return false;
|
|
156
|
+
if (res.ok) return true;
|
|
157
|
+
throw blossomHttpError("HEAD", url, res);
|
|
158
|
+
}
|
|
159
|
+
/** GET body, then verifyBlob. Non-2xx or sha256 mismatch → throw BlossomError. */
|
|
160
|
+
async function getBlob(server, sha256Hex, opts) {
|
|
161
|
+
const hash = assertHex32(sha256Hex, "blob sha256");
|
|
162
|
+
const url = blossomUrl(server, `/${hash}`);
|
|
163
|
+
const res = await blossomFetch(opts?.fetch, url, {
|
|
164
|
+
method: "GET",
|
|
165
|
+
signal: opts?.signal
|
|
166
|
+
});
|
|
167
|
+
if (!res.ok) throw blossomHttpError("GET", url, res);
|
|
168
|
+
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
169
|
+
if (!await verifyBlob(bytes, hash)) throw new BlossomError("blob sha256 mismatch");
|
|
170
|
+
return bytes;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* If `url` is not available, try `servers` for the same hash (NIP-B7 SHOULD).
|
|
174
|
+
* Never throws on HTTP status. AbortError still throws.
|
|
175
|
+
* Returns the original URL when no hash, original still available, or nothing else responds.
|
|
176
|
+
*/
|
|
177
|
+
async function healBlobUrl(url, servers, opts) {
|
|
178
|
+
const hash = getHashFromURL(url);
|
|
179
|
+
if (!hash) return url;
|
|
180
|
+
const ext = extensionFromHashUrl(url, hash);
|
|
181
|
+
const fetchImpl = opts?.fetch ?? requireGlobalFetch(missingBlossomFetch);
|
|
182
|
+
const originalStatus = await headStatus(fetchImpl, url, opts?.signal);
|
|
183
|
+
if (originalStatus !== void 0 && originalStatus >= 200 && originalStatus < 400) return url;
|
|
184
|
+
for (const server of servers) {
|
|
185
|
+
const base = parseHttpUrl(server);
|
|
186
|
+
if (!base) continue;
|
|
187
|
+
const candidate = `${base}/${hash}${ext}`;
|
|
188
|
+
const status = await headStatus(fetchImpl, candidate, opts?.signal);
|
|
189
|
+
if (status !== void 0 && status >= 200 && status < 300) return candidate;
|
|
190
|
+
}
|
|
191
|
+
return url;
|
|
192
|
+
}
|
|
193
|
+
/** PUT /upload to each server in order until one descriptor succeeds. */
|
|
194
|
+
async function uploadToServers(servers, file, auth, opts) {
|
|
195
|
+
if (servers.length === 0) throw new BlossomError("no servers");
|
|
196
|
+
let last;
|
|
197
|
+
for (const server of servers) try {
|
|
198
|
+
return await upload(server, file, auth, opts);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
if (isAbortError(err)) throw err;
|
|
201
|
+
last = err instanceof BlossomError ? err : new BlossomError("blossom upload failed", { cause: err instanceof Error ? err : void 0 });
|
|
202
|
+
}
|
|
203
|
+
throw last ?? new BlossomError("no servers");
|
|
204
|
+
}
|
|
205
|
+
function blossomServerListEventBuilder(servers) {
|
|
206
|
+
const tags = [];
|
|
207
|
+
const seen = /* @__PURE__ */ new Set();
|
|
208
|
+
for (const raw of servers) {
|
|
209
|
+
const url = parseHttpUrl(raw);
|
|
210
|
+
if (!url || seen.has(url)) continue;
|
|
211
|
+
seen.add(url);
|
|
212
|
+
tags.push(["server", url]);
|
|
213
|
+
}
|
|
214
|
+
return new EventBuilder(Kind.BlossomServerList, "").tags(tags);
|
|
215
|
+
}
|
|
216
|
+
function parseBlossomServerList(event) {
|
|
217
|
+
if (event.kind !== Kind.BlossomServerList) throw new EventValidationError(`expected kind ${Kind.BlossomServerList}, got ${event.kind}`);
|
|
218
|
+
const out = [];
|
|
219
|
+
const seen = /* @__PURE__ */ new Set();
|
|
220
|
+
for (const tag of event.tags) {
|
|
221
|
+
if (tag[0] !== "server" || !tag[1]) continue;
|
|
222
|
+
const url = parseHttpUrl(tag[1]);
|
|
223
|
+
if (!url || seen.has(url)) continue;
|
|
224
|
+
seen.add(url);
|
|
225
|
+
out.push(url);
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
function parseHttpUrl(raw) {
|
|
230
|
+
try {
|
|
231
|
+
const u = new URL(raw);
|
|
232
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return void 0;
|
|
233
|
+
const path = u.pathname.replace(/\/+$/, "");
|
|
234
|
+
return `${u.origin}${path}`;
|
|
235
|
+
} catch {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/** Join `/upload` etc. onto the stored server, keeping a path prefix (`/v1/upload`). */
|
|
240
|
+
function blossomUrl(server, path) {
|
|
241
|
+
const base = parseHttpUrl(server);
|
|
242
|
+
if (!base) throw new BlossomError(`invalid blossom server URL: ${server}`);
|
|
243
|
+
return `${base}${path.startsWith("/") ? path : `/${path}`}`;
|
|
244
|
+
}
|
|
245
|
+
function isAbortError(err) {
|
|
246
|
+
return err instanceof Error && err.name === "AbortError";
|
|
247
|
+
}
|
|
248
|
+
/** Path suffix after the last 64-hex segment (`HASH.png` → `.png`). */
|
|
249
|
+
function extensionFromHashUrl(url, hash) {
|
|
250
|
+
let pathname;
|
|
251
|
+
try {
|
|
252
|
+
pathname = new URL(url).pathname;
|
|
253
|
+
} catch {
|
|
254
|
+
return "";
|
|
255
|
+
}
|
|
256
|
+
const i = pathname.toLowerCase().lastIndexOf(hash);
|
|
257
|
+
if (i < 0) return "";
|
|
258
|
+
return pathname.slice(i + 64).match(/^\.[^/]+/)?.[0] ?? "";
|
|
259
|
+
}
|
|
260
|
+
function missingBlossomFetch() {
|
|
261
|
+
return new BlossomError("no fetch implementation available; pass opts.fetch");
|
|
262
|
+
}
|
|
263
|
+
function blossomHttpError(method, url, res) {
|
|
264
|
+
let reason;
|
|
265
|
+
try {
|
|
266
|
+
reason = res.headers.get("x-reason") ?? void 0;
|
|
267
|
+
} catch {
|
|
268
|
+
reason = void 0;
|
|
269
|
+
}
|
|
270
|
+
return new BlossomError(reason?.trim() || `blossom ${method} ${url} failed (${res.status})`, { status: res.status });
|
|
271
|
+
}
|
|
272
|
+
async function blossomFetch(fetchImpl, url, init) {
|
|
273
|
+
return fetchManual(fetchImpl ?? requireGlobalFetch(missingBlossomFetch), url, init, (err) => new BlossomError(`blossom ${init.method} ${url} failed`, { cause: err instanceof Error ? err : void 0 }));
|
|
274
|
+
}
|
|
275
|
+
/** HEAD with redirect:manual. Network → undefined. AbortError propagates. Never throws on HTTP status. */
|
|
276
|
+
async function headStatus(fetchImpl, url, signal) {
|
|
277
|
+
try {
|
|
278
|
+
return (await sendManual(fetchImpl, url, {
|
|
279
|
+
method: "HEAD",
|
|
280
|
+
signal
|
|
281
|
+
})).status;
|
|
282
|
+
} catch (err) {
|
|
283
|
+
if (isAbortError(err)) throw err;
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function blossomRequest(fetchImpl, url, init) {
|
|
288
|
+
const res = await blossomFetch(fetchImpl, url, init);
|
|
289
|
+
if (res.ok) return res;
|
|
290
|
+
throw blossomHttpError(init.method, url, res);
|
|
291
|
+
}
|
|
292
|
+
async function readJson(res) {
|
|
293
|
+
try {
|
|
294
|
+
return await res.json();
|
|
295
|
+
} catch (cause) {
|
|
296
|
+
throw new BlossomError("invalid blossom JSON response", {
|
|
297
|
+
cause: cause instanceof Error ? cause : void 0,
|
|
298
|
+
status: res.status
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function parseBlobDescriptor(json) {
|
|
303
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) throw new BlossomError("invalid blob descriptor");
|
|
304
|
+
const raw = json;
|
|
305
|
+
if (typeof raw.url !== "string" || !raw.url) throw new BlossomError("blob descriptor missing url");
|
|
306
|
+
if (typeof raw.sha256 !== "string") throw new BlossomError("blob descriptor missing sha256");
|
|
307
|
+
const hash = assertHex32(raw.sha256, "blob sha256");
|
|
308
|
+
if (typeof raw.size !== "number" || !Number.isInteger(raw.size) || raw.size < 0) throw new BlossomError("blob descriptor missing size");
|
|
309
|
+
const desc = {
|
|
310
|
+
url: raw.url,
|
|
311
|
+
sha256: hash,
|
|
312
|
+
size: raw.size
|
|
313
|
+
};
|
|
314
|
+
if (typeof raw.type === "string") desc.type = raw.type;
|
|
315
|
+
if (typeof raw.uploaded === "number" && Number.isInteger(raw.uploaded) && raw.uploaded >= 0) desc.uploaded = raw.uploaded;
|
|
316
|
+
return desc;
|
|
317
|
+
}
|
|
318
|
+
function requireDescriptorHash(desc, expected) {
|
|
319
|
+
if (desc.sha256 !== expected) throw new BlossomError("blob descriptor sha256 mismatch");
|
|
320
|
+
return desc;
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
export { BlossomError, blobExists, blossomServerListEventBuilder, checkUpload, createAuthTemplate, createUploadAuth, deleteBlob, encodeAuthorizationHeader, getBlob, getHashFromURL, healBlobUrl, listBlobs, mirrorBlob, parseBlossomServerList, sha256Blob, upload, uploadToServers, verifyBlob };
|
|
324
|
+
|
|
325
|
+
//# sourceMappingURL=blossom.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blossom.mjs","names":[],"sources":["../../src/nips/blossom.ts"],"sourcesContent":["/**\n * Blossom BUD-01/02/04/06 HTTP helpers, NIP-B7 kind 10063 server lists, and URL healing.\n * Auth is kind 24242, not NIP-98; the Authorization value is base64url.\n *\n * @see https://github.com/hzrd149/blossom\n * @see https://github.com/nostr-protocol/nips/blob/master/B7.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\nimport { base64urlnopad } from \"@scure/base\";\nimport { EventBuilder } from \"../core/builder.ts\";\nimport { EventValidationError, NostrError } from \"../core/error.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport type { Tag } from \"../core/tag.ts\";\nimport { assertHex32, bytesToHex, utf8Encoder } from \"../core/util.ts\";\nimport { fetchManual, requireGlobalFetch, sendManual, type ManualFetch } from \"./http.ts\";\n\nexport type BlobDescriptor = {\n url: string;\n sha256: string;\n size: number;\n type?: string;\n uploaded?: number;\n};\n\nexport type BlossomFetch = ManualFetch;\n\n/** Signs a kind 24242 auth template (draft; pubkey is filled by the signer). */\nexport type BlossomSign = (template: EventTemplate) => Promise<Event>;\n\n/** BUD-11 `t` verbs. Mirror reuses `upload`; there is no `t=mirror`. */\nexport type BlossomAuthVerb = \"upload\" | \"delete\" | \"list\" | \"get\" | \"media\";\n\nconst AUTH_VERBS = new Set<BlossomAuthVerb>([\"upload\", \"delete\", \"list\", \"get\", \"media\"]);\n\n/** Default BUD-11 expiration: now + 1h. Servers 401 when the tag is missing. */\nconst AUTH_EXPIRATION_SECS = 3600;\n\nexport class BlossomError extends NostrError {\n readonly status?: number;\n\n constructor(message: string, options?: ErrorOptions & { status?: number }) {\n const { status, ...rest } = options ?? {};\n super(message, rest);\n this.status = status;\n }\n}\n\n/** Last 64-char hex in the URL path (extension ignored). Null if none or the URL is invalid. */\nexport function getHashFromURL(url: string | URL): string | null {\n let path: string;\n try {\n path = (url instanceof URL ? url : new URL(url)).pathname;\n } catch {\n return null;\n }\n const matches = path.match(/[0-9a-f]{64}/gi);\n if (!matches?.length) return null;\n return matches[matches.length - 1]!.toLowerCase();\n}\n\nexport async function sha256Blob(data: Blob | ArrayBuffer | Uint8Array): Promise<string> {\n let bytes: Uint8Array;\n if (data instanceof Uint8Array) {\n bytes = data;\n } else if (data instanceof ArrayBuffer) {\n bytes = new Uint8Array(data);\n } else {\n bytes = new Uint8Array(await data.arrayBuffer());\n }\n return bytesToHex(sha256(bytes));\n}\n\n/** True when `sha256Blob(data)` equals the expected hex (any case). */\nexport async function verifyBlob(\n data: Blob | ArrayBuffer | Uint8Array,\n sha256Hex: string,\n): Promise<boolean> {\n const expected = assertHex32(sha256Hex, \"blob sha256\");\n return (await sha256Blob(data)) === expected;\n}\n\n/**\n * `Nostr ` + base64url(utf8 JSON), padding stripped.\n * NIP-98 uses standard base64; mixing them is a 401.\n */\nexport function encodeAuthorizationHeader(event: Event): string {\n return `Nostr ${base64urlnopad.encode(utf8Encoder.encode(JSON.stringify(event)))}`;\n}\n\nexport function createAuthTemplate(\n verb: BlossomAuthVerb,\n opts?: { sha256?: string; expiration?: number; message?: string },\n): EventTemplate {\n if (!AUTH_VERBS.has(verb)) {\n throw new BlossomError(`invalid blossom auth verb: ${verb}`);\n }\n const expiration = opts?.expiration ?? Math.floor(Date.now() / 1000) + AUTH_EXPIRATION_SECS;\n const tags: Tag[] = [\n [\"t\", verb],\n [\"expiration\", String(expiration)],\n ];\n if (opts?.sha256 !== undefined) {\n tags.push([\"x\", assertHex32(opts.sha256, \"blob sha256\")]);\n }\n return {\n kind: Kind.BlobsAuth,\n created_at: Math.floor(Date.now() / 1000),\n tags,\n content: opts?.message ?? \"\",\n };\n}\n\nexport async function createUploadAuth(\n sign: BlossomSign,\n file: Blob,\n opts?: { message?: string; expiration?: number },\n): Promise<Event> {\n const hash = await sha256Blob(file);\n return sign(createAuthTemplate(\"upload\", { sha256: hash, ...opts }));\n}\n\nexport async function upload(\n server: string,\n file: Blob,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor> {\n const hash = await sha256Blob(file);\n const headers: Record<string, string> = {\n Authorization: encodeAuthorizationHeader(auth),\n \"X-SHA-256\": hash,\n };\n if (file.type) headers[\"Content-Type\"] = file.type;\n const res = await blossomRequest(opts?.fetch, blossomUrl(server, \"/upload\"), {\n method: \"PUT\",\n headers,\n body: file,\n signal: opts?.signal,\n });\n return requireDescriptorHash(parseBlobDescriptor(await readJson(res)), hash);\n}\n\nexport async function listBlobs(\n server: string,\n pubkey: string,\n auth: Event | undefined,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor[]> {\n const pk = assertHex32(pubkey, \"pubkey\");\n const headers: Record<string, string> = {};\n if (auth) headers.Authorization = encodeAuthorizationHeader(auth);\n const res = await blossomRequest(opts?.fetch, blossomUrl(server, `/list/${pk}`), {\n method: \"GET\",\n headers,\n signal: opts?.signal,\n });\n const json = await readJson(res);\n if (!Array.isArray(json)) {\n throw new BlossomError(\"blossom list response must be a JSON array\", { status: res.status });\n }\n return json.map(parseBlobDescriptor);\n}\n\nexport async function deleteBlob(\n server: string,\n sha256Hex: string,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<void> {\n const hash = assertHex32(sha256Hex, \"blob sha256\");\n await blossomRequest(opts?.fetch, blossomUrl(server, `/${hash}`), {\n method: \"DELETE\",\n headers: { Authorization: encodeAuthorizationHeader(auth) },\n signal: opts?.signal,\n });\n}\n\nexport async function mirrorBlob(\n server: string,\n blob: BlobDescriptor,\n opts: { auth: Event; fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor> {\n const hash = assertHex32(blob.sha256, \"blob sha256\");\n const headers: Record<string, string> = {\n Authorization: encodeAuthorizationHeader(opts.auth),\n \"Content-Type\": \"application/json\",\n \"X-SHA-256\": hash,\n \"X-Content-Length\": String(blob.size),\n };\n if (blob.type) headers[\"X-Content-Type\"] = blob.type;\n const res = await blossomRequest(opts.fetch, blossomUrl(server, \"/mirror\"), {\n method: \"PUT\",\n headers,\n body: JSON.stringify({ url: blob.url }),\n signal: opts.signal,\n });\n return requireDescriptorHash(parseBlobDescriptor(await readJson(res)), hash);\n}\n\n/** BUD-06 preflight. Does not PUT the body. */\nexport async function checkUpload(\n server: string,\n file: Blob,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<void> {\n const hash = await sha256Blob(file);\n const headers: Record<string, string> = {\n Authorization: encodeAuthorizationHeader(auth),\n \"X-SHA-256\": hash,\n \"X-Content-Length\": String(file.size),\n };\n if (file.type) headers[\"X-Content-Type\"] = file.type;\n await blossomRequest(opts?.fetch, blossomUrl(server, \"/upload\"), {\n method: \"HEAD\",\n headers,\n signal: opts?.signal,\n });\n}\n\n/** Strict HEAD probe. 2xx → true. 404 → false. Other HTTP (including 405) / network → throw BlossomError. AbortError propagates. No GET fallback. */\nexport async function blobExists(\n server: string,\n sha256Hex: string,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<boolean> {\n const hash = assertHex32(sha256Hex, \"blob sha256\");\n const url = blossomUrl(server, `/${hash}`);\n const res = await blossomFetch(opts?.fetch, url, { method: \"HEAD\", signal: opts?.signal });\n if (res.status === 404) return false;\n if (res.ok) return true;\n throw blossomHttpError(\"HEAD\", url, res);\n}\n\n/** GET body, then verifyBlob. Non-2xx or sha256 mismatch → throw BlossomError. */\nexport async function getBlob(\n server: string,\n sha256Hex: string,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<Uint8Array> {\n const hash = assertHex32(sha256Hex, \"blob sha256\");\n const url = blossomUrl(server, `/${hash}`);\n const res = await blossomFetch(opts?.fetch, url, { method: \"GET\", signal: opts?.signal });\n if (!res.ok) throw blossomHttpError(\"GET\", url, res);\n const bytes = new Uint8Array(await res.arrayBuffer());\n if (!(await verifyBlob(bytes, hash))) {\n throw new BlossomError(\"blob sha256 mismatch\");\n }\n return bytes;\n}\n\n/**\n * If `url` is not available, try `servers` for the same hash (NIP-B7 SHOULD).\n * Never throws on HTTP status. AbortError still throws.\n * Returns the original URL when no hash, original still available, or nothing else responds.\n */\nexport async function healBlobUrl(\n url: string,\n servers: readonly string[],\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<string> {\n const hash = getHashFromURL(url);\n if (!hash) return url;\n\n const ext = extensionFromHashUrl(url, hash);\n const fetchImpl = opts?.fetch ?? requireGlobalFetch(missingBlossomFetch);\n\n const originalStatus = await headStatus(fetchImpl, url, opts?.signal);\n // 2xx/3xx: still available. NIP-B7 only heals URLs that are not.\n if (originalStatus !== undefined && originalStatus >= 200 && originalStatus < 400) {\n return url;\n }\n\n for (const server of servers) {\n const base = parseHttpUrl(server);\n if (!base) continue;\n const candidate = `${base}/${hash}${ext}`;\n const status = await headStatus(fetchImpl, candidate, opts?.signal);\n if (status !== undefined && status >= 200 && status < 300) return candidate;\n }\n return url;\n}\n\n/** PUT /upload to each server in order until one descriptor succeeds. */\nexport async function uploadToServers(\n servers: readonly string[],\n file: Blob,\n auth: Event,\n opts?: { fetch?: BlossomFetch; signal?: AbortSignal },\n): Promise<BlobDescriptor> {\n if (servers.length === 0) {\n throw new BlossomError(\"no servers\");\n }\n let last: BlossomError | undefined;\n for (const server of servers) {\n try {\n return await upload(server, file, auth, opts);\n } catch (err) {\n if (isAbortError(err)) throw err;\n last =\n err instanceof BlossomError\n ? err\n : new BlossomError(\"blossom upload failed\", {\n cause: err instanceof Error ? err : undefined,\n });\n }\n }\n throw last ?? new BlossomError(\"no servers\");\n}\n\nexport function blossomServerListEventBuilder(servers: readonly string[]): EventBuilder {\n const tags: Tag[] = [];\n const seen = new Set<string>();\n for (const raw of servers) {\n const url = parseHttpUrl(raw);\n if (!url || seen.has(url)) continue;\n seen.add(url);\n tags.push([\"server\", url]);\n }\n return new EventBuilder(Kind.BlossomServerList, \"\").tags(tags);\n}\n\nexport function parseBlossomServerList(event: Pick<Event, \"kind\" | \"tags\">): string[] {\n if (event.kind !== Kind.BlossomServerList) {\n throw new EventValidationError(`expected kind ${Kind.BlossomServerList}, got ${event.kind}`);\n }\n const out: string[] = [];\n const seen = new Set<string>();\n for (const tag of event.tags) {\n if (tag[0] !== \"server\" || !tag[1]) continue;\n const url = parseHttpUrl(tag[1]);\n if (!url || seen.has(url)) continue;\n seen.add(url);\n out.push(url);\n }\n return out;\n}\n\nfunction parseHttpUrl(raw: string): string | undefined {\n try {\n const u = new URL(raw);\n if (u.protocol !== \"http:\" && u.protocol !== \"https:\") return undefined;\n const path = u.pathname.replace(/\\/+$/, \"\");\n return `${u.origin}${path}`;\n } catch {\n return undefined;\n }\n}\n\n/** Join `/upload` etc. onto the stored server, keeping a path prefix (`/v1/upload`). */\nfunction blossomUrl(server: string, path: string): string {\n const base = parseHttpUrl(server);\n if (!base) {\n throw new BlossomError(`invalid blossom server URL: ${server}`);\n }\n const suffix = path.startsWith(\"/\") ? path : `/${path}`;\n return `${base}${suffix}`;\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === \"AbortError\";\n}\n\n/** Path suffix after the last 64-hex segment (`HASH.png` → `.png`). */\nfunction extensionFromHashUrl(url: string, hash: string): string {\n let pathname: string;\n try {\n pathname = new URL(url).pathname;\n } catch {\n return \"\";\n }\n const i = pathname.toLowerCase().lastIndexOf(hash);\n if (i < 0) return \"\";\n const m = pathname.slice(i + 64).match(/^\\.[^/]+/);\n return m?.[0] ?? \"\";\n}\n\nfunction missingBlossomFetch(): BlossomError {\n return new BlossomError(\"no fetch implementation available; pass opts.fetch\");\n}\n\ntype ManualResponse = Awaited<ReturnType<ManualFetch>>;\n\nfunction blossomHttpError(method: string, url: string, res: ManualResponse): BlossomError {\n let reason: string | undefined;\n try {\n reason = res.headers.get(\"x-reason\") ?? undefined;\n } catch {\n reason = undefined;\n }\n return new BlossomError(reason?.trim() || `blossom ${method} ${url} failed (${res.status})`, {\n status: res.status,\n });\n}\n\nasync function blossomFetch(\n fetchImpl: BlossomFetch | undefined,\n url: string,\n init: {\n method: string;\n headers?: Record<string, string>;\n body?: Blob | string | null;\n signal?: AbortSignal;\n },\n): Promise<ManualResponse> {\n return fetchManual(\n fetchImpl ?? requireGlobalFetch(missingBlossomFetch),\n url,\n init,\n (err) =>\n new BlossomError(`blossom ${init.method} ${url} failed`, {\n cause: err instanceof Error ? err : undefined,\n }),\n );\n}\n\n/** HEAD with redirect:manual. Network → undefined. AbortError propagates. Never throws on HTTP status. */\nasync function headStatus(\n fetchImpl: ManualFetch,\n url: string,\n signal?: AbortSignal,\n): Promise<number | undefined> {\n try {\n const res = await sendManual(fetchImpl, url, { method: \"HEAD\", signal });\n return res.status;\n } catch (err) {\n if (isAbortError(err)) throw err;\n return undefined;\n }\n}\n\nasync function blossomRequest(\n fetchImpl: BlossomFetch | undefined,\n url: string,\n init: {\n method: string;\n headers?: Record<string, string>;\n body?: Blob | string | null;\n signal?: AbortSignal;\n },\n): Promise<ManualResponse> {\n const res = await blossomFetch(fetchImpl, url, init);\n if (res.ok) return res;\n throw blossomHttpError(init.method, url, res);\n}\n\nasync function readJson(res: ManualResponse): Promise<unknown> {\n try {\n return await res.json();\n } catch (cause) {\n throw new BlossomError(\"invalid blossom JSON response\", {\n cause: cause instanceof Error ? cause : undefined,\n status: res.status,\n });\n }\n}\n\nfunction parseBlobDescriptor(json: unknown): BlobDescriptor {\n if (!json || typeof json !== \"object\" || Array.isArray(json)) {\n throw new BlossomError(\"invalid blob descriptor\");\n }\n const raw = json as Record<string, unknown>;\n if (typeof raw.url !== \"string\" || !raw.url) {\n throw new BlossomError(\"blob descriptor missing url\");\n }\n if (typeof raw.sha256 !== \"string\") {\n throw new BlossomError(\"blob descriptor missing sha256\");\n }\n const hash = assertHex32(raw.sha256, \"blob sha256\");\n if (typeof raw.size !== \"number\" || !Number.isInteger(raw.size) || raw.size < 0) {\n throw new BlossomError(\"blob descriptor missing size\");\n }\n const desc: BlobDescriptor = { url: raw.url, sha256: hash, size: raw.size };\n if (typeof raw.type === \"string\") desc.type = raw.type;\n if (typeof raw.uploaded === \"number\" && Number.isInteger(raw.uploaded) && raw.uploaded >= 0) {\n desc.uploaded = raw.uploaded;\n }\n return desc;\n}\n\nfunction requireDescriptorHash(desc: BlobDescriptor, expected: string): BlobDescriptor {\n if (desc.sha256 !== expected) {\n throw new BlossomError(\"blob descriptor sha256 mismatch\");\n }\n return desc;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAiCA,MAAM,6BAAa,IAAI,IAAqB;CAAC;CAAU;CAAU;CAAQ;CAAO;AAAO,CAAC;;AAGxF,MAAM,uBAAuB;AAE7B,IAAa,eAAb,cAAkC,WAAW;CAC3C;CAEA,YAAY,SAAiB,SAA8C;EACzE,MAAM,EAAE,QAAQ,GAAG,SAAS,WAAW,CAAC;EACxC,MAAM,SAAS,IAAI;EACnB,KAAK,SAAS;CAChB;AACF;;AAGA,SAAgB,eAAe,KAAkC;CAC/D,IAAI;CACJ,IAAI;EACF,QAAQ,eAAe,MAAM,MAAM,IAAI,IAAI,GAAG,EAAA,CAAG;CACnD,QAAQ;EACN,OAAO;CACT;CACA,MAAM,UAAU,KAAK,MAAM,gBAAgB;CAC3C,IAAI,CAAC,SAAS,QAAQ,OAAO;CAC7B,OAAO,QAAQ,QAAQ,SAAS,EAAE,CAAE,YAAY;AAClD;AAEA,eAAsB,WAAW,MAAwD;CACvF,IAAI;CACJ,IAAI,gBAAgB,YAClB,QAAQ;MACH,IAAI,gBAAgB,aACzB,QAAQ,IAAI,WAAW,IAAI;MAE3B,QAAQ,IAAI,WAAW,MAAM,KAAK,YAAY,CAAC;CAEjD,OAAO,WAAW,OAAO,KAAK,CAAC;AACjC;;AAGA,eAAsB,WACpB,MACA,WACkB;CAClB,MAAM,WAAW,YAAY,WAAW,aAAa;CACrD,OAAQ,MAAM,WAAW,IAAI,MAAO;AACtC;;;;;AAMA,SAAgB,0BAA0B,OAAsB;CAC9D,OAAO,SAAS,eAAe,OAAO,YAAY,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC;AACjF;AAEA,SAAgB,mBACd,MACA,MACe;CACf,IAAI,CAAC,WAAW,IAAI,IAAI,GACtB,MAAM,IAAI,aAAa,8BAA8B,MAAM;CAE7D,MAAM,aAAa,MAAM,cAAc,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,IAAI;CACvE,MAAM,OAAc,CAClB,CAAC,KAAK,IAAI,GACV,CAAC,cAAc,OAAO,UAAU,CAAC,CACnC;CACA,IAAI,MAAM,WAAW,KAAA,GACnB,KAAK,KAAK,CAAC,KAAK,YAAY,KAAK,QAAQ,aAAa,CAAC,CAAC;CAE1D,OAAO;EACL,MAAM,KAAK;EACX,YAAY,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI;EACxC;EACA,SAAS,MAAM,WAAW;CAC5B;AACF;AAEA,eAAsB,iBACpB,MACA,MACA,MACgB;CAEhB,OAAO,KAAK,mBAAmB,UAAU;EAAE,QAAQ,MADhC,WAAW,IAAI;EACuB,GAAG;CAAK,CAAC,CAAC;AACrE;AAEA,eAAsB,OACpB,QACA,MACA,MACA,MACyB;CACzB,MAAM,OAAO,MAAM,WAAW,IAAI;CAClC,MAAM,UAAkC;EACtC,eAAe,0BAA0B,IAAI;EAC7C,aAAa;CACf;CACA,IAAI,KAAK,MAAM,QAAQ,kBAAkB,KAAK;CAO9C,OAAO,sBAAsB,oBAAoB,MAAM,SAAS,MAN9C,eAAe,MAAM,OAAO,WAAW,QAAQ,SAAS,GAAG;EAC3E,QAAQ;EACR;EACA,MAAM;EACN,QAAQ,MAAM;CAChB,CAAC,CACkE,CAAC,GAAG,IAAI;AAC7E;AAEA,eAAsB,UACpB,QACA,QACA,MACA,MAC2B;CAC3B,MAAM,KAAK,YAAY,QAAQ,QAAQ;CACvC,MAAM,UAAkC,CAAC;CACzC,IAAI,MAAM,QAAQ,gBAAgB,0BAA0B,IAAI;CAChE,MAAM,MAAM,MAAM,eAAe,MAAM,OAAO,WAAW,QAAQ,SAAS,IAAI,GAAG;EAC/E,QAAQ;EACR;EACA,QAAQ,MAAM;CAChB,CAAC;CACD,MAAM,OAAO,MAAM,SAAS,GAAG;CAC/B,IAAI,CAAC,MAAM,QAAQ,IAAI,GACrB,MAAM,IAAI,aAAa,8CAA8C,EAAE,QAAQ,IAAI,OAAO,CAAC;CAE7F,OAAO,KAAK,IAAI,mBAAmB;AACrC;AAEA,eAAsB,WACpB,QACA,WACA,MACA,MACe;CACf,MAAM,OAAO,YAAY,WAAW,aAAa;CACjD,MAAM,eAAe,MAAM,OAAO,WAAW,QAAQ,IAAI,MAAM,GAAG;EAChE,QAAQ;EACR,SAAS,EAAE,eAAe,0BAA0B,IAAI,EAAE;EAC1D,QAAQ,MAAM;CAChB,CAAC;AACH;AAEA,eAAsB,WACpB,QACA,MACA,MACyB;CACzB,MAAM,OAAO,YAAY,KAAK,QAAQ,aAAa;CACnD,MAAM,UAAkC;EACtC,eAAe,0BAA0B,KAAK,IAAI;EAClD,gBAAgB;EAChB,aAAa;EACb,oBAAoB,OAAO,KAAK,IAAI;CACtC;CACA,IAAI,KAAK,MAAM,QAAQ,oBAAoB,KAAK;CAOhD,OAAO,sBAAsB,oBAAoB,MAAM,SAAS,MAN9C,eAAe,KAAK,OAAO,WAAW,QAAQ,SAAS,GAAG;EAC1E,QAAQ;EACR;EACA,MAAM,KAAK,UAAU,EAAE,KAAK,KAAK,IAAI,CAAC;EACtC,QAAQ,KAAK;CACf,CAAC,CACkE,CAAC,GAAG,IAAI;AAC7E;;AAGA,eAAsB,YACpB,QACA,MACA,MACA,MACe;CACf,MAAM,OAAO,MAAM,WAAW,IAAI;CAClC,MAAM,UAAkC;EACtC,eAAe,0BAA0B,IAAI;EAC7C,aAAa;EACb,oBAAoB,OAAO,KAAK,IAAI;CACtC;CACA,IAAI,KAAK,MAAM,QAAQ,oBAAoB,KAAK;CAChD,MAAM,eAAe,MAAM,OAAO,WAAW,QAAQ,SAAS,GAAG;EAC/D,QAAQ;EACR;EACA,QAAQ,MAAM;CAChB,CAAC;AACH;;AAGA,eAAsB,WACpB,QACA,WACA,MACkB;CAElB,MAAM,MAAM,WAAW,QAAQ,IADlB,YAAY,WAAW,aACE,GAAG;CACzC,MAAM,MAAM,MAAM,aAAa,MAAM,OAAO,KAAK;EAAE,QAAQ;EAAQ,QAAQ,MAAM;CAAO,CAAC;CACzF,IAAI,IAAI,WAAW,KAAK,OAAO;CAC/B,IAAI,IAAI,IAAI,OAAO;CACnB,MAAM,iBAAiB,QAAQ,KAAK,GAAG;AACzC;;AAGA,eAAsB,QACpB,QACA,WACA,MACqB;CACrB,MAAM,OAAO,YAAY,WAAW,aAAa;CACjD,MAAM,MAAM,WAAW,QAAQ,IAAI,MAAM;CACzC,MAAM,MAAM,MAAM,aAAa,MAAM,OAAO,KAAK;EAAE,QAAQ;EAAO,QAAQ,MAAM;CAAO,CAAC;CACxF,IAAI,CAAC,IAAI,IAAI,MAAM,iBAAiB,OAAO,KAAK,GAAG;CACnD,MAAM,QAAQ,IAAI,WAAW,MAAM,IAAI,YAAY,CAAC;CACpD,IAAI,CAAE,MAAM,WAAW,OAAO,IAAI,GAChC,MAAM,IAAI,aAAa,sBAAsB;CAE/C,OAAO;AACT;;;;;;AAOA,eAAsB,YACpB,KACA,SACA,MACiB;CACjB,MAAM,OAAO,eAAe,GAAG;CAC/B,IAAI,CAAC,MAAM,OAAO;CAElB,MAAM,MAAM,qBAAqB,KAAK,IAAI;CAC1C,MAAM,YAAY,MAAM,SAAS,mBAAmB,mBAAmB;CAEvE,MAAM,iBAAiB,MAAM,WAAW,WAAW,KAAK,MAAM,MAAM;CAEpE,IAAI,mBAAmB,KAAA,KAAa,kBAAkB,OAAO,iBAAiB,KAC5E,OAAO;CAGT,KAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,OAAO,aAAa,MAAM;EAChC,IAAI,CAAC,MAAM;EACX,MAAM,YAAY,GAAG,KAAK,GAAG,OAAO;EACpC,MAAM,SAAS,MAAM,WAAW,WAAW,WAAW,MAAM,MAAM;EAClE,IAAI,WAAW,KAAA,KAAa,UAAU,OAAO,SAAS,KAAK,OAAO;CACpE;CACA,OAAO;AACT;;AAGA,eAAsB,gBACpB,SACA,MACA,MACA,MACyB;CACzB,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,aAAa,YAAY;CAErC,IAAI;CACJ,KAAK,MAAM,UAAU,SACnB,IAAI;EACF,OAAO,MAAM,OAAO,QAAQ,MAAM,MAAM,IAAI;CAC9C,SAAS,KAAK;EACZ,IAAI,aAAa,GAAG,GAAG,MAAM;EAC7B,OACE,eAAe,eACX,MACA,IAAI,aAAa,yBAAyB,EACxC,OAAO,eAAe,QAAQ,MAAM,KAAA,EACtC,CAAC;CACT;CAEF,MAAM,QAAQ,IAAI,aAAa,YAAY;AAC7C;AAEA,SAAgB,8BAA8B,SAA0C;CACtF,MAAM,OAAc,CAAC;CACrB,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,OAAO,SAAS;EACzB,MAAM,MAAM,aAAa,GAAG;EAC5B,IAAI,CAAC,OAAO,KAAK,IAAI,GAAG,GAAG;EAC3B,KAAK,IAAI,GAAG;EACZ,KAAK,KAAK,CAAC,UAAU,GAAG,CAAC;CAC3B;CACA,OAAO,IAAI,aAAa,KAAK,mBAAmB,EAAE,CAAC,CAAC,KAAK,IAAI;AAC/D;AAEA,SAAgB,uBAAuB,OAA+C;CACpF,IAAI,MAAM,SAAS,KAAK,mBACtB,MAAM,IAAI,qBAAqB,iBAAiB,KAAK,kBAAkB,QAAQ,MAAM,MAAM;CAE7F,MAAM,MAAgB,CAAC;CACvB,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,OAAO,MAAM,MAAM;EAC5B,IAAI,IAAI,OAAO,YAAY,CAAC,IAAI,IAAI;EACpC,MAAM,MAAM,aAAa,IAAI,EAAE;EAC/B,IAAI,CAAC,OAAO,KAAK,IAAI,GAAG,GAAG;EAC3B,KAAK,IAAI,GAAG;EACZ,IAAI,KAAK,GAAG;CACd;CACA,OAAO;AACT;AAEA,SAAS,aAAa,KAAiC;CACrD,IAAI;EACF,MAAM,IAAI,IAAI,IAAI,GAAG;EACrB,IAAI,EAAE,aAAa,WAAW,EAAE,aAAa,UAAU,OAAO,KAAA;EAC9D,MAAM,OAAO,EAAE,SAAS,QAAQ,QAAQ,EAAE;EAC1C,OAAO,GAAG,EAAE,SAAS;CACvB,QAAQ;EACN;CACF;AACF;;AAGA,SAAS,WAAW,QAAgB,MAAsB;CACxD,MAAM,OAAO,aAAa,MAAM;CAChC,IAAI,CAAC,MACH,MAAM,IAAI,aAAa,+BAA+B,QAAQ;CAGhE,OAAO,GAAG,OADK,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI;AAEnD;AAEA,SAAS,aAAa,KAAuB;CAC3C,OAAO,eAAe,SAAS,IAAI,SAAS;AAC9C;;AAGA,SAAS,qBAAqB,KAAa,MAAsB;CAC/D,IAAI;CACJ,IAAI;EACF,WAAW,IAAI,IAAI,GAAG,CAAC,CAAC;CAC1B,QAAQ;EACN,OAAO;CACT;CACA,MAAM,IAAI,SAAS,YAAY,CAAC,CAAC,YAAY,IAAI;CACjD,IAAI,IAAI,GAAG,OAAO;CAElB,OADU,SAAS,MAAM,IAAI,EAAE,CAAC,CAAC,MAAM,UAChC,CAAC,GAAG,MAAM;AACnB;AAEA,SAAS,sBAAoC;CAC3C,OAAO,IAAI,aAAa,oDAAoD;AAC9E;AAIA,SAAS,iBAAiB,QAAgB,KAAa,KAAmC;CACxF,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,QAAQ,IAAI,UAAU,KAAK,KAAA;CAC1C,QAAQ;EACN,SAAS,KAAA;CACX;CACA,OAAO,IAAI,aAAa,QAAQ,KAAK,KAAK,WAAW,OAAO,GAAG,IAAI,WAAW,IAAI,OAAO,IAAI,EAC3F,QAAQ,IAAI,OACd,CAAC;AACH;AAEA,eAAe,aACb,WACA,KACA,MAMyB;CACzB,OAAO,YACL,aAAa,mBAAmB,mBAAmB,GACnD,KACA,OACC,QACC,IAAI,aAAa,WAAW,KAAK,OAAO,GAAG,IAAI,UAAU,EACvD,OAAO,eAAe,QAAQ,MAAM,KAAA,EACtC,CAAC,CACL;AACF;;AAGA,eAAe,WACb,WACA,KACA,QAC6B;CAC7B,IAAI;EAEF,QAAO,MADW,WAAW,WAAW,KAAK;GAAE,QAAQ;GAAQ;EAAO,CAAC,EAAA,CAC5D;CACb,SAAS,KAAK;EACZ,IAAI,aAAa,GAAG,GAAG,MAAM;EAC7B;CACF;AACF;AAEA,eAAe,eACb,WACA,KACA,MAMyB;CACzB,MAAM,MAAM,MAAM,aAAa,WAAW,KAAK,IAAI;CACnD,IAAI,IAAI,IAAI,OAAO;CACnB,MAAM,iBAAiB,KAAK,QAAQ,KAAK,GAAG;AAC9C;AAEA,eAAe,SAAS,KAAuC;CAC7D,IAAI;EACF,OAAO,MAAM,IAAI,KAAK;CACxB,SAAS,OAAO;EACd,MAAM,IAAI,aAAa,iCAAiC;GACtD,OAAO,iBAAiB,QAAQ,QAAQ,KAAA;GACxC,QAAQ,IAAI;EACd,CAAC;CACH;AACF;AAEA,SAAS,oBAAoB,MAA+B;CAC1D,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GACzD,MAAM,IAAI,aAAa,yBAAyB;CAElD,MAAM,MAAM;CACZ,IAAI,OAAO,IAAI,QAAQ,YAAY,CAAC,IAAI,KACtC,MAAM,IAAI,aAAa,6BAA6B;CAEtD,IAAI,OAAO,IAAI,WAAW,UACxB,MAAM,IAAI,aAAa,gCAAgC;CAEzD,MAAM,OAAO,YAAY,IAAI,QAAQ,aAAa;CAClD,IAAI,OAAO,IAAI,SAAS,YAAY,CAAC,OAAO,UAAU,IAAI,IAAI,KAAK,IAAI,OAAO,GAC5E,MAAM,IAAI,aAAa,8BAA8B;CAEvD,MAAM,OAAuB;EAAE,KAAK,IAAI;EAAK,QAAQ;EAAM,MAAM,IAAI;CAAK;CAC1E,IAAI,OAAO,IAAI,SAAS,UAAU,KAAK,OAAO,IAAI;CAClD,IAAI,OAAO,IAAI,aAAa,YAAY,OAAO,UAAU,IAAI,QAAQ,KAAK,IAAI,YAAY,GACxF,KAAK,WAAW,IAAI;CAEtB,OAAO;AACT;AAEA,SAAS,sBAAsB,MAAsB,UAAkC;CACrF,IAAI,KAAK,WAAW,UAClB,MAAM,IAAI,aAAa,iCAAiC;CAE1D,OAAO;AACT"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
//#region src/nips/nip04.d.ts
|
|
2
|
+
/** Encrypt plaintext to a peer pubkey (NIP-04). */
|
|
3
|
+
declare function encrypt(secretKey: string | Uint8Array, pubkey: string, text: string): string;
|
|
4
|
+
/** Decrypt a NIP-04 payload from a peer pubkey. */
|
|
5
|
+
declare function decrypt(secretKey: string | Uint8Array, pubkey: string, data: string): string;
|
|
6
|
+
//#endregion
|
|
7
|
+
export { decrypt, encrypt };
|
|
8
|
+
//# sourceMappingURL=nip04.d.mts.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { c as utf8Decoder, i as hexToBytes, l as utf8Encoder, t as assertHex32 } from "../util-6tfLQ9JF.mjs";
|
|
2
|
+
import { t as CryptoError } from "../error-DZEs5OuZ.mjs";
|
|
3
|
+
import { secp256k1 } from "@noble/curves/secp256k1.js";
|
|
4
|
+
import { cbc } from "@noble/ciphers/aes.js";
|
|
5
|
+
import { randomBytes } from "@noble/hashes/utils.js";
|
|
6
|
+
import { base64 } from "@scure/base";
|
|
7
|
+
//#region src/nips/nip04.ts
|
|
8
|
+
/**
|
|
9
|
+
* NIP-04 legacy encrypted direct messages.
|
|
10
|
+
* Prefer NIP-44 for new applications.
|
|
11
|
+
*/
|
|
12
|
+
function normalizeSharedSecret(privkey, pubkey) {
|
|
13
|
+
assertHex32(pubkey, "public key");
|
|
14
|
+
return secp256k1.getSharedSecret(privkey, hexToBytes("02" + pubkey.toLowerCase())).slice(1, 33);
|
|
15
|
+
}
|
|
16
|
+
function resolveSecret(secretKey) {
|
|
17
|
+
return typeof secretKey === "string" ? hexToBytes(assertHex32(secretKey, "secret key")) : secretKey;
|
|
18
|
+
}
|
|
19
|
+
/** Encrypt plaintext to a peer pubkey (NIP-04). */
|
|
20
|
+
function encrypt(secretKey, pubkey, text) {
|
|
21
|
+
const normalizedKey = normalizeSharedSecret(resolveSecret(secretKey), pubkey);
|
|
22
|
+
const iv = randomBytes(16);
|
|
23
|
+
const plaintext = utf8Encoder.encode(text);
|
|
24
|
+
const ciphertext = cbc(normalizedKey, iv).encrypt(plaintext);
|
|
25
|
+
return `${base64.encode(ciphertext)}?iv=${base64.encode(iv)}`;
|
|
26
|
+
}
|
|
27
|
+
/** Decrypt a NIP-04 payload from a peer pubkey. */
|
|
28
|
+
function decrypt(secretKey, pubkey, data) {
|
|
29
|
+
const privkey = resolveSecret(secretKey);
|
|
30
|
+
const parts = data.split("?iv=");
|
|
31
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) throw new CryptoError("invalid NIP-04 payload: missing iv");
|
|
32
|
+
const normalizedKey = normalizeSharedSecret(privkey, pubkey);
|
|
33
|
+
const iv = base64.decode(parts[1]);
|
|
34
|
+
const ciphertext = base64.decode(parts[0]);
|
|
35
|
+
const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext);
|
|
36
|
+
return utf8Decoder.decode(plaintext);
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { decrypt, encrypt };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=nip04.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nip04.mjs","names":[],"sources":["../../src/nips/nip04.ts"],"sourcesContent":["/**\n * NIP-04 legacy encrypted direct messages.\n * Prefer NIP-44 for new applications.\n */\nimport { cbc } from \"@noble/ciphers/aes.js\";\nimport { secp256k1 } from \"@noble/curves/secp256k1.js\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\nimport { base64 } from \"@scure/base\";\nimport { CryptoError } from \"../core/error.ts\";\nimport { assertHex32, hexToBytes, utf8Decoder, utf8Encoder } from \"../core/util.ts\";\n\nfunction normalizeSharedSecret(privkey: Uint8Array, pubkey: string): Uint8Array {\n assertHex32(pubkey, \"public key\");\n const key = secp256k1.getSharedSecret(privkey, hexToBytes(\"02\" + pubkey.toLowerCase()));\n return key.slice(1, 33);\n}\n\nfunction resolveSecret(secretKey: string | Uint8Array): Uint8Array {\n return typeof secretKey === \"string\"\n ? hexToBytes(assertHex32(secretKey, \"secret key\"))\n : secretKey;\n}\n\n/** Encrypt plaintext to a peer pubkey (NIP-04). */\nexport function encrypt(secretKey: string | Uint8Array, pubkey: string, text: string): string {\n const privkey = resolveSecret(secretKey);\n const normalizedKey = normalizeSharedSecret(privkey, pubkey);\n const iv = randomBytes(16);\n const plaintext = utf8Encoder.encode(text);\n const ciphertext = cbc(normalizedKey, iv).encrypt(plaintext);\n return `${base64.encode(ciphertext)}?iv=${base64.encode(iv)}`;\n}\n\n/** Decrypt a NIP-04 payload from a peer pubkey. */\nexport function decrypt(secretKey: string | Uint8Array, pubkey: string, data: string): string {\n const privkey = resolveSecret(secretKey);\n const parts = data.split(\"?iv=\");\n if (parts.length !== 2 || !parts[0] || !parts[1]) {\n throw new CryptoError(\"invalid NIP-04 payload: missing iv\");\n }\n const normalizedKey = normalizeSharedSecret(privkey, pubkey);\n const iv = base64.decode(parts[1]);\n const ciphertext = base64.decode(parts[0]);\n const plaintext = cbc(normalizedKey, iv).decrypt(ciphertext);\n return utf8Decoder.decode(plaintext);\n}\n"],"mappings":";;;;;;;;;;;AAWA,SAAS,sBAAsB,SAAqB,QAA4B;CAC9E,YAAY,QAAQ,YAAY;CAEhC,OADY,UAAU,gBAAgB,SAAS,WAAW,OAAO,OAAO,YAAY,CAAC,CAC5E,CAAC,CAAC,MAAM,GAAG,EAAE;AACxB;AAEA,SAAS,cAAc,WAA4C;CACjE,OAAO,OAAO,cAAc,WACxB,WAAW,YAAY,WAAW,YAAY,CAAC,IAC/C;AACN;;AAGA,SAAgB,QAAQ,WAAgC,QAAgB,MAAsB;CAE5F,MAAM,gBAAgB,sBADN,cAAc,SACoB,GAAG,MAAM;CAC3D,MAAM,KAAK,YAAY,EAAE;CACzB,MAAM,YAAY,YAAY,OAAO,IAAI;CACzC,MAAM,aAAa,IAAI,eAAe,EAAE,CAAC,CAAC,QAAQ,SAAS;CAC3D,OAAO,GAAG,OAAO,OAAO,UAAU,EAAE,MAAM,OAAO,OAAO,EAAE;AAC5D;;AAGA,SAAgB,QAAQ,WAAgC,QAAgB,MAAsB;CAC5F,MAAM,UAAU,cAAc,SAAS;CACvC,MAAM,QAAQ,KAAK,MAAM,MAAM;CAC/B,IAAI,MAAM,WAAW,KAAK,CAAC,MAAM,MAAM,CAAC,MAAM,IAC5C,MAAM,IAAI,YAAY,oCAAoC;CAE5D,MAAM,gBAAgB,sBAAsB,SAAS,MAAM;CAC3D,MAAM,KAAK,OAAO,OAAO,MAAM,EAAE;CACjC,MAAM,aAAa,OAAO,OAAO,MAAM,EAAE;CACzC,MAAM,YAAY,IAAI,eAAe,EAAE,CAAC,CAAC,QAAQ,UAAU;CAC3D,OAAO,YAAY,OAAO,SAAS;AACrC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { a as NostrError } from "../error-WEFS-R-4.mjs";
|
|
2
|
+
import { f as ProfilePointer } from "../nip19-D7905oIv.mjs";
|
|
3
|
+
import { t as ManualFetch } from "../http-6vevbBlu.mjs";
|
|
4
|
+
//#region src/nips/nip05.d.ts
|
|
5
|
+
/** Root local-part (`_@domain` rendered as just the domain). */
|
|
6
|
+
declare const NIP05_ROOT_LOCAL = "_";
|
|
7
|
+
declare const WELL_KNOWN_PATH = "/.well-known/nostr.json";
|
|
8
|
+
/**
|
|
9
|
+
* NIP-05 identifier string.
|
|
10
|
+
* - Full: `name@domain`
|
|
11
|
+
* - Root: `domain` or `_@domain`
|
|
12
|
+
*/
|
|
13
|
+
type Nip05 = string;
|
|
14
|
+
type Nip05Address = {
|
|
15
|
+
/** Local part (lowercased). `_` for domain-only identifiers. */
|
|
16
|
+
local: string;
|
|
17
|
+
/** Domain (lowercased). */
|
|
18
|
+
domain: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 46.md appendix discovery metadata (`{relays, nostrconnect_url}`).
|
|
22
|
+
* Not a bunker pointer.
|
|
23
|
+
*/
|
|
24
|
+
type Nip05Nip46 = {
|
|
25
|
+
relays?: string[];
|
|
26
|
+
nostrconnectUrl?: string;
|
|
27
|
+
};
|
|
28
|
+
type Nip05Document = {
|
|
29
|
+
names: Record<string, string>;
|
|
30
|
+
/** NIP-05 profile relay hints */
|
|
31
|
+
relays?: Record<string, string[]>;
|
|
32
|
+
nip46?: Nip05Nip46;
|
|
33
|
+
};
|
|
34
|
+
type Nip05Fetch = ManualFetch;
|
|
35
|
+
declare class Nip05Error extends NostrError {
|
|
36
|
+
constructor(message: string, options?: ErrorOptions);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Matches optional local@domain.
|
|
40
|
+
* Groups: 1=local (optional), 2=domain.
|
|
41
|
+
*/
|
|
42
|
+
declare const NIP05_REGEX: RegExp;
|
|
43
|
+
declare function isNip05(value: unknown): value is string;
|
|
44
|
+
/**
|
|
45
|
+
* Parse an identifier into local + domain.
|
|
46
|
+
* Accepts `name@domain`, `_@domain`, or bare `domain` (local becomes `_`).
|
|
47
|
+
*/
|
|
48
|
+
declare function parseNip05(input: string): Nip05Address;
|
|
49
|
+
/** Build the well-known HTTPS URL for an address. */
|
|
50
|
+
declare function wellKnownUrl(address: Nip05Address): string;
|
|
51
|
+
/**
|
|
52
|
+
* Parse nostr.json `nip46`: 46.md appendix `{relays, nostrconnect_url}`.
|
|
53
|
+
* Hex-pubkey maps and other keys are ignored.
|
|
54
|
+
*/
|
|
55
|
+
declare function parseNip05Nip46(raw: unknown): Nip05Nip46 | undefined;
|
|
56
|
+
/** Parse and validate a nostr.json document body. */
|
|
57
|
+
declare function parseNip05Document(json: unknown): Nip05Document;
|
|
58
|
+
/** Resolve local name from an already-parsed document. */
|
|
59
|
+
declare function lookupFromDocument(doc: Nip05Document, address: Nip05Address): ProfilePointer | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Fetch and parse `/.well-known/nostr.json`.
|
|
62
|
+
* Returns `null` on network/parse failure (does not throw for those).
|
|
63
|
+
* Rejects HTTP redirects (non-200) per NIP-05 security constraints.
|
|
64
|
+
* AbortError rethrows; it is not mapped to `null`.
|
|
65
|
+
*/
|
|
66
|
+
declare function queryNip05Document(identifier: string, opts?: {
|
|
67
|
+
fetch?: Nip05Fetch;
|
|
68
|
+
signal?: AbortSignal;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
address: Nip05Address;
|
|
71
|
+
doc: Nip05Document;
|
|
72
|
+
} | null>;
|
|
73
|
+
/**
|
|
74
|
+
* Query `/.well-known/nostr.json` for an identifier.
|
|
75
|
+
* Returns `null` on network/parse/lookup failure (does not throw for those).
|
|
76
|
+
* Profile `relays` only. `nip46` is discovery metadata, not a bunker pointer.
|
|
77
|
+
*/
|
|
78
|
+
declare function queryProfile(identifier: string, opts?: {
|
|
79
|
+
fetch?: Nip05Fetch;
|
|
80
|
+
signal?: AbortSignal;
|
|
81
|
+
}): Promise<ProfilePointer | null>;
|
|
82
|
+
/** True when the identifier resolves to exactly `pubkey`. */
|
|
83
|
+
declare function verifyNip05(pubkey: string, identifier: string, opts?: {
|
|
84
|
+
fetch?: Nip05Fetch;
|
|
85
|
+
signal?: AbortSignal;
|
|
86
|
+
}): Promise<boolean>;
|
|
87
|
+
//#endregion
|
|
88
|
+
export { NIP05_REGEX, NIP05_ROOT_LOCAL, Nip05, Nip05Address, Nip05Document, Nip05Error, Nip05Fetch, Nip05Nip46, WELL_KNOWN_PATH, isNip05, lookupFromDocument, parseNip05, parseNip05Document, parseNip05Nip46, queryNip05Document, queryProfile, verifyNip05, wellKnownUrl };
|
|
89
|
+
//# sourceMappingURL=nip05.d.mts.map
|