@qntx/nostr 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-DgkqDylK.d.mts → builder-CTwkB2zM.d.mts} +10 -10
- package/dist/{builder-CdLE4J6O.mjs → builder-aNU2gGFP.mjs} +4 -4
- package/dist/builder-aNU2gGFP.mjs.map +1 -0
- package/dist/{client-CILhdfsF.mjs → client-Cj2lUaVh.mjs} +70 -72
- package/dist/client-Cj2lUaVh.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/core.d.mts +2 -2
- package/dist/core.mjs +6 -6
- package/dist/emitter-DkCY6NkU.mjs +32 -0
- package/dist/emitter-DkCY6NkU.mjs.map +1 -0
- package/dist/{error-L_QdmSnV.mjs → error-BILMe3Gr.mjs} +2 -2
- package/dist/{error-L_QdmSnV.mjs.map → error-BILMe3Gr.mjs.map} +1 -1
- package/dist/{error-gtJvV6YS.mjs → error-DlhGW4mJ.mjs} +2 -2
- package/dist/{error-gtJvV6YS.mjs.map → error-DlhGW4mJ.mjs.map} +1 -1
- package/dist/{event-DinQMmJ1.mjs → event-CHf47dPM.mjs} +2 -2
- package/dist/{event-DinQMmJ1.mjs.map → event-CHf47dPM.mjs.map} +1 -1
- package/dist/{gossip-CcTp9VpU.mjs → gossip-DFxIBlsr.mjs} +3 -3
- package/dist/gossip-DFxIBlsr.mjs.map +1 -0
- package/dist/{gossip-C7HLW5kI.d.mts → gossip-OOeeFzG5.d.mts} +4 -4
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/{index-InQZpHyQ.d.mts → index-BxCBX-8R.d.mts} +2 -2
- package/dist/{index-iLT8UzY3.d.mts → index-CPVEyzNX.d.mts} +22 -22
- package/dist/{index-Tx15HGAD.d.mts → index-C_w2WfVz.d.mts} +4 -4
- package/dist/{index-DocgD9K0.d.mts → index-Cl_1XdYP.d.mts} +29 -23
- package/dist/{index-DiXwNn2M.d.mts → index-DyKwQnbt.d.mts} +2 -2
- package/dist/index.d.mts +14 -14
- package/dist/index.mjs +17 -17
- package/dist/{instance-BcKzxHIP.mjs → instance-C62w3cEt.mjs} +3 -3
- package/dist/{instance-BcKzxHIP.mjs.map → instance-C62w3cEt.mjs.map} +1 -1
- package/dist/{key-bshqfmZL.mjs → key-DnVpchl6.mjs} +3 -3
- package/dist/{key-bshqfmZL.mjs.map → key-DnVpchl6.mjs.map} +1 -1
- package/dist/{loaders-DD0XAGzw.mjs → loaders-BmPkQfzI.mjs} +6 -6
- package/dist/loaders-BmPkQfzI.mjs.map +1 -0
- package/dist/loaders.d.mts +2 -2
- package/dist/loaders.mjs +1 -1
- package/dist/{memory-D_-5-T31.mjs → memory-CdJ07kub.mjs} +2 -2
- package/dist/memory-CdJ07kub.mjs.map +1 -0
- package/dist/{memory-index-C6rujtoc.mjs → memory-index-Cbc83vOZ.mjs} +3 -3
- package/dist/{memory-index-C6rujtoc.mjs.map → memory-index-Cbc83vOZ.mjs.map} +1 -1
- package/dist/{message-CWLcFTQ1.mjs → message-oNr7t0SJ.mjs} +3 -3
- package/dist/message-oNr7t0SJ.mjs.map +1 -0
- package/dist/{message-CbBZOhq3.d.mts → message-tclYCVNc.d.mts} +4 -4
- package/dist/{nip17-cTDFfFVt.d.mts → nip17-B5WgW4C4.d.mts} +4 -4
- package/dist/{nip19-D1OnJ192.d.mts → nip19-DpPN17bY.d.mts} +23 -23
- package/dist/{nip46-DrsPI8EX.d.mts → nip46-BHaxC9TZ.d.mts} +17 -17
- package/dist/{nip51-DoPUvQ--.d.mts → nip51-CR0Q8eym.d.mts} +10 -10
- package/dist/{nip59-BFIihLYY.d.mts → nip59-C-RWFQIv.d.mts} +9 -2
- package/dist/{nip65-52HiwiyD.d.mts → nip65-D1fbmhOg.d.mts} +2 -2
- package/dist/{nip77-C9QcZn7b.d.mts → nip77-DzUwUxHq.d.mts} +6 -6
- package/dist/nips/blossom.d.mts +6 -6
- package/dist/nips/blossom.mjs +2 -2
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.mjs +1 -1
- package/dist/nips/nip05.d.mts +8 -8
- package/dist/nips/nip05.mjs +1 -1
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +7 -7
- package/dist/nips/nip10.mjs +34 -32
- package/dist/nips/nip10.mjs.map +1 -1
- package/dist/nips/nip11.d.mts +27 -27
- package/dist/nips/nip11.mjs +1 -1
- package/dist/nips/nip11.mjs.map +1 -1
- package/dist/nips/nip13.mjs +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +6 -4
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +1 -1
- package/dist/nips/nip19.mjs +1 -1
- package/dist/nips/nip19.mjs.map +1 -1
- package/dist/nips/nip21.d.mts +4 -4
- package/dist/nips/nip21.mjs +1 -1
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +19 -19
- package/dist/nips/nip27.mjs +2 -2
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.mjs +1 -1
- package/dist/nips/nip44.mjs +2 -2
- package/dist/nips/nip44.mjs.map +1 -1
- package/dist/nips/nip46.d.mts +1 -1
- package/dist/nips/nip46.mjs +1 -1
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +4 -4
- package/dist/nips/nip49.mjs +1 -1
- package/dist/nips/nip49.mjs.map +1 -1
- package/dist/nips/nip51.d.mts +1 -1
- package/dist/nips/nip51.mjs +3 -3
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.d.mts +18 -18
- package/dist/nips/nip57.mjs +3 -3
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +15 -7
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +2 -2
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +1 -1
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip98.mjs +3 -3
- package/dist/{pool-SzJKPIa3.d.mts → pool-CqQvUnsq.d.mts} +43 -24
- package/dist/{pool-YHRZgcQ3.mjs → pool-vlw74C4t.mjs} +39 -29
- package/dist/pool-vlw74C4t.mjs.map +1 -0
- package/dist/{put-DS0yzVJx.mjs → put-w13ci3sC.mjs} +3 -3
- package/dist/put-w13ci3sC.mjs.map +1 -0
- package/dist/{reactive-BJRoVHxU.mjs → reactive-Tz0otDIP.mjs} +49 -27
- package/dist/reactive-Tz0otDIP.mjs.map +1 -0
- package/dist/{reactive-BJ1ggn4d.d.mts → reactive-gGqUrzW8.d.mts} +16 -7
- package/dist/{relay-CvxPiQ_m.mjs → relay-quCs7MHD.mjs} +1049 -861
- package/dist/relay-quCs7MHD.mjs.map +1 -0
- package/dist/relay.d.mts +4 -4
- package/dist/relay.mjs +2 -2
- package/dist/{signer-yW3slDC2.mjs → signer-poTGe6Er.mjs} +6 -6
- package/dist/signer-poTGe6Er.mjs.map +1 -0
- package/dist/signer.d.mts +1 -1
- package/dist/signer.mjs +2 -2
- package/dist/{storage-CZ52ifcQ.mjs → storage-B5IXTVuS.mjs} +16 -14
- package/dist/storage-B5IXTVuS.mjs.map +1 -0
- package/dist/storage.d.mts +2 -2
- package/dist/storage.mjs +4 -4
- package/dist/store.d.mts +2 -2
- package/dist/store.mjs +1 -1
- package/dist/testing.d.mts +3 -3
- package/dist/testing.mjs +6 -6
- package/dist/testing.mjs.map +1 -1
- package/dist/{types-Cm-XMgtH.d.mts → types-BacwnDtU.d.mts} +5 -5
- package/dist/{util-yFYzlbrm.mjs → util-DMF9AIO0.mjs} +6 -2
- package/dist/util-DMF9AIO0.mjs.map +1 -0
- package/dist/{verifier-BTLOpo1n.mjs → verifier-B51Wkt5H.mjs} +3 -3
- package/dist/{verifier-BTLOpo1n.mjs.map → verifier-B51Wkt5H.mjs.map} +1 -1
- package/dist/wasm.browser.mjs +2 -2
- package/dist/wasm.mjs +2 -2
- package/package.json +1 -1
- package/dist/builder-CdLE4J6O.mjs.map +0 -1
- package/dist/client-CILhdfsF.mjs.map +0 -1
- package/dist/gossip-CcTp9VpU.mjs.map +0 -1
- package/dist/loaders-DD0XAGzw.mjs.map +0 -1
- package/dist/memory-D_-5-T31.mjs.map +0 -1
- package/dist/message-CWLcFTQ1.mjs.map +0 -1
- package/dist/pool-YHRZgcQ3.mjs.map +0 -1
- package/dist/put-DS0yzVJx.mjs.map +0 -1
- package/dist/reactive-BJRoVHxU.mjs.map +0 -1
- package/dist/relay-CvxPiQ_m.mjs.map +0 -1
- package/dist/signer-yW3slDC2.mjs.map +0 -1
- package/dist/storage-CZ52ifcQ.mjs.map +0 -1
- package/dist/util-yFYzlbrm.mjs.map +0 -1
package/dist/nips/nip59.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { c as isRecord, d as nowSeconds, n as assertHex32, v as NostrError } from "../util-
|
|
1
|
+
import { c as isRecord, d as nowSeconds, n as assertHex32, v as NostrError } from "../util-DMF9AIO0.mjs";
|
|
2
2
|
import { t as Kind } from "../kind-Cxwhhkx8.mjs";
|
|
3
|
-
import { h as Tag, m as validateSignedEvent, n as getEventHash, p as validateEvent } from "../event-
|
|
4
|
-
import { r as finalizeEvent, t as Keys } from "../key-
|
|
5
|
-
import { n as verifyEvent } from "../verifier-
|
|
3
|
+
import { h as Tag, m as validateSignedEvent, n as getEventHash, p as validateEvent } from "../event-CHf47dPM.mjs";
|
|
4
|
+
import { r as finalizeEvent, t as Keys } from "../key-DnVpchl6.mjs";
|
|
5
|
+
import { n as verifyEvent } from "../verifier-B51Wkt5H.mjs";
|
|
6
6
|
import { encryptToPubkey } from "./nip44.mjs";
|
|
7
7
|
import { randomBytes } from "@noble/hashes/utils.js";
|
|
8
8
|
//#region src/nips/nip59.ts
|
|
@@ -57,6 +57,10 @@ function eventToJson(event) {
|
|
|
57
57
|
sig: event.sig
|
|
58
58
|
});
|
|
59
59
|
}
|
|
60
|
+
function assertExpiration(ts) {
|
|
61
|
+
if (!Number.isSafeInteger(ts) || ts < 0) throw new Nip59Error("expiration must be a non-negative safe integer");
|
|
62
|
+
return ts;
|
|
63
|
+
}
|
|
60
64
|
function defaultRandomInt(maxExclusive) {
|
|
61
65
|
if (!Number.isSafeInteger(maxExclusive) || maxExclusive <= 0 || maxExclusive > 4294967296) throw new Nip59Error("randomInt bound must be a positive integer within uint32 range");
|
|
62
66
|
const limit = Math.floor(4294967296 / maxExclusive) * maxExclusive;
|
|
@@ -91,12 +95,14 @@ async function createSeal(crypto, recipient, rumor, opts) {
|
|
|
91
95
|
throw new Nip59Error("failed to encrypt", { cause: error });
|
|
92
96
|
}
|
|
93
97
|
const created_at = opts?.timestamps?.seal ?? (opts?.randomize === "wrap" ? rumor.created_at : randomPastTimestamp(opts));
|
|
98
|
+
const tags = [];
|
|
99
|
+
if (opts?.expiration !== void 0) tags.push(["expiration", String(assertExpiration(opts.expiration))]);
|
|
94
100
|
const pubkey = await crypto.getPublicKey();
|
|
95
101
|
return crypto.signEvent({
|
|
96
102
|
kind: Kind.Seal,
|
|
97
103
|
content,
|
|
98
104
|
created_at,
|
|
99
|
-
tags
|
|
105
|
+
tags,
|
|
100
106
|
pubkey
|
|
101
107
|
});
|
|
102
108
|
}
|
|
@@ -105,9 +111,11 @@ function createGiftWrap(seal, recipient, opts) {
|
|
|
105
111
|
const ephemeral = Keys.generate();
|
|
106
112
|
const content = encryptToPubkey(eventToJson(seal), ephemeral.secretKey.bytes, recipientPk);
|
|
107
113
|
const created_at = opts?.timestamps?.wrap ?? randomPastTimestamp(opts);
|
|
108
|
-
const tags = [Tag.p(recipientPk, opts?.relayHint)
|
|
114
|
+
const tags = [Tag.p(recipientPk, opts?.relayHint)];
|
|
115
|
+
if (opts?.expiration !== void 0) tags.push(["expiration", String(assertExpiration(opts.expiration))]);
|
|
116
|
+
tags.push(...opts?.extraTags ?? []);
|
|
109
117
|
return finalizeEvent({
|
|
110
|
-
kind: Kind.GiftWrap,
|
|
118
|
+
kind: opts?.ephemeral === true ? Kind.GiftWrapEphemeral : Kind.GiftWrap,
|
|
111
119
|
content,
|
|
112
120
|
created_at,
|
|
113
121
|
tags
|
package/dist/nips/nip59.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nip59.mjs","names":["TagBuilder"],"sources":["../../src/nips/nip59.ts"],"sourcesContent":["/**\n * NIP-59: Gift Wrap. Rumor → NIP-44 seal (kind 13) → gift wrap (kind 1059). Does not import signer,\n * relay, or client. Crypto is a structural type.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/59.md\n */\nimport { randomBytes } from \"@noble/hashes/utils.js\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event, UnsignedEvent } from \"../core/event.ts\";\nimport { getEventHash, validateEvent, validateSignedEvent } from \"../core/event.ts\";\nimport { Keys, finalizeEvent } from \"../core/key.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport type { Tag } from \"../core/tag.ts\";\nimport { Tag as TagBuilder } from \"../core/tag.ts\";\nimport { assertHex32, isRecord, nowSeconds } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\nimport { encryptToPubkey } from \"./nip44.ts\";\n\n/** Unsigned event with a computed id. Never has `sig`. */\nexport type Rumor = UnsignedEvent & {\n readonly id: string;\n};\n\n/**\n * Structural crypto used by NIP-59. Satisfied by NostrSigner when nip44Encrypt/nip44Decrypt are\n * present. This module must not import src/signer/.\n */\nexport type Nip59Crypto = {\n getPublicKey: () => Promise<string>;\n signEvent: (unsigned: UnsignedEvent) => Promise<Event>;\n nip44Encrypt: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type Nip44Decryptor = {\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type GiftWrapTimestamps = {\n readonly seal: number;\n readonly wrap: number;\n};\n\n/** `\"wrap\"` randomizes only the gift wrap; `\"seal+wrap\"` (default) randomizes both. */\nexport type TimestampRandomize = \"wrap\" | \"seal+wrap\";\n\nexport type WrapOptions = {\n /** Unix seconds used as the randomization window end. Default: floor(Date.now()/1000). */\n readonly now?: number | undefined;\n /** Uniform integer in [0, maxExclusive). Default: CSPRNG via @noble/hashes randomBytes. */\n readonly randomInt?: ((maxExclusive: number) => number) | undefined;\n /** When set, used as-is. Overrides now/randomInt for this call. */\n readonly timestamps?: GiftWrapTimestamps | undefined;\n readonly relayHint?: string | undefined;\n /** Appended after the required wrap `p` tag. Never applied to the seal. */\n readonly extraTags?: ReadonlyArray<Tag> | undefined;\n /**\n * Default `\"seal+wrap\"` (NIP-59). `\"wrap\"` = only wrap timestamp is randomized; seal uses\n * rumor.created_at.\n */\n readonly randomize?: TimestampRandomize | undefined;\n};\n\nexport type SealOptions = Pick<WrapOptions, \"now\" | \"randomInt\" | \"timestamps\" | \"randomize\">;\n\nexport class Nip59Error extends NostrError {\n override name = \"Nip59Error\";\n}\n\nexport const TWO_DAYS_SECS: number = 2 * 24 * 60 * 60;\n\nexport function isGiftWrapKind(kind: number): boolean {\n return kind === Kind.GiftWrap || kind === Kind.GiftWrapEphemeral;\n}\n\ntype Nip59CryptoInput = {\n getPublicKey: () => Promise<string>;\n signEvent: (unsigned: UnsignedEvent) => Promise<Event>;\n nip44Encrypt?: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt?: (peer: string, payload: string) => Promise<string>;\n};\n\n/** Narrow a signer-shaped object to {@link Nip59Crypto}, or throw. */\nexport function requireNip59Crypto(crypto: Nip59CryptoInput): Nip59Crypto {\n const encrypt = crypto.nip44Encrypt;\n const decrypt = crypto.nip44Decrypt;\n if (!encrypt || !decrypt) {\n throw new Nip59Error(\"NIP-44 is required\");\n }\n return {\n getPublicKey: async () => crypto.getPublicKey(),\n signEvent: async (unsigned) => crypto.signEvent(unsigned),\n nip44Encrypt: async (peer, plaintext) => encrypt.call(crypto, peer, plaintext),\n nip44Decrypt: async (peer, payload) => decrypt.call(crypto, peer, payload),\n };\n}\n\nexport function requireNip44Decryptor(crypto: {\n nip44Decrypt?: (peer: string, payload: string) => Promise<string>;\n}): Nip44Decryptor {\n const decrypt = crypto.nip44Decrypt;\n if (!decrypt) {\n throw new Nip59Error(\"NIP-44 is required\");\n }\n return {\n nip44Decrypt: async (peer, payload) => decrypt.call(crypto, peer, payload),\n };\n}\n\nexport function rumorToJson(rumor: Rumor): string {\n return JSON.stringify({\n id: rumor.id,\n pubkey: rumor.pubkey,\n created_at: rumor.created_at,\n kind: rumor.kind,\n tags: rumor.tags,\n content: rumor.content,\n });\n}\n\nexport function eventToJson(event: Event): string {\n return JSON.stringify({\n id: event.id,\n pubkey: event.pubkey,\n created_at: event.created_at,\n kind: event.kind,\n tags: event.tags,\n content: event.content,\n sig: event.sig,\n });\n}\n\nfunction defaultRandomInt(maxExclusive: number): number {\n if (!Number.isSafeInteger(maxExclusive) || maxExclusive <= 0 || maxExclusive > 0x100000000) {\n throw new Nip59Error(\"randomInt bound must be a positive integer within uint32 range\");\n }\n // rejection sampling: discard draws ≥ the largest multiple of maxExclusive that fits uint32\n const limit = Math.floor(0x100000000 / maxExclusive) * maxExclusive;\n for (;;) {\n const bytes = randomBytes(4);\n const n = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);\n if (n < limit) {\n return n % maxExclusive;\n }\n }\n}\n\nexport function randomPastTimestamp(opts?: {\n now?: number | undefined;\n randomInt?: ((maxExclusive: number) => number) | undefined;\n}): number {\n const now = opts?.now ?? nowSeconds();\n const offset = (opts?.randomInt ?? defaultRandomInt)(TWO_DAYS_SECS);\n return now - offset;\n}\n\nexport function createRumor(\n pubkey: string,\n template: {\n kind: number;\n content?: string | undefined;\n tags?: ReadonlyArray<Tag> | undefined;\n created_at?: number | undefined;\n },\n): Rumor {\n const unsigned: UnsignedEvent = {\n kind: template.kind,\n content: template.content ?? \"\",\n tags: template.tags ? [...template.tags] : [],\n created_at: template.created_at ?? nowSeconds(),\n pubkey: assertHex32(pubkey, \"public key\"),\n };\n return { ...unsigned, id: getEventHash(unsigned) };\n}\n\nexport async function createSeal(\n crypto: Nip59Crypto,\n recipient: string,\n rumor: Rumor,\n opts?: SealOptions,\n): Promise<Event> {\n const recipientPk = assertHex32(recipient, \"public key\");\n let content: string;\n try {\n content = await crypto.nip44Encrypt(recipientPk, rumorToJson(rumor));\n } catch (error) {\n throw new Nip59Error(\"failed to encrypt\", { cause: error });\n }\n const created_at =\n opts?.timestamps?.seal ??\n (opts?.randomize === \"wrap\" ? rumor.created_at : randomPastTimestamp(opts));\n const pubkey = await crypto.getPublicKey();\n return crypto.signEvent({\n kind: Kind.Seal,\n content,\n created_at,\n tags: [],\n pubkey,\n });\n}\n\nexport function createGiftWrap(seal: Event, recipient: string, opts?: WrapOptions): Event {\n const recipientPk = assertHex32(recipient, \"public key\");\n const ephemeral = Keys.generate();\n const content = encryptToPubkey(eventToJson(seal), ephemeral.secretKey.bytes, recipientPk);\n const created_at = opts?.timestamps?.wrap ?? randomPastTimestamp(opts);\n const tags: Tag[] = [TagBuilder.p(recipientPk, opts?.relayHint), ...(opts?.extraTags ?? [])];\n return finalizeEvent(\n {\n kind: Kind.GiftWrap,\n content,\n created_at,\n tags,\n },\n ephemeral.secretKey,\n );\n}\n\nexport async function wrap(\n crypto: Nip59Crypto,\n recipient: string,\n rumor: Rumor,\n opts?: WrapOptions,\n): Promise<Event> {\n const seal = await createSeal(crypto, recipient, rumor, opts);\n return createGiftWrap(seal, recipient, opts);\n}\n\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch (error) {\n throw new Nip59Error(\"invalid JSON\", { cause: error });\n }\n}\n\nfunction parseRumor(value: unknown): Rumor {\n if (!isRecord(value)) {\n throw new Nip59Error(\"invalid rumor\");\n }\n if (Object.hasOwn(value, \"sig\")) {\n throw new Nip59Error(\"rumor must be unsigned\");\n }\n const givenId = value[\"id\"];\n if (!validateEvent(value)) {\n throw new Nip59Error(\"invalid rumor\");\n }\n const unsigned: UnsignedEvent = {\n kind: value.kind,\n tags: value.tags,\n content: value.content,\n created_at: value.created_at,\n pubkey: value.pubkey.toLowerCase(),\n };\n const expected = getEventHash(unsigned);\n if (typeof givenId === \"string\" && givenId.toLowerCase() !== expected) {\n throw new Nip59Error(\"invalid rumor\");\n }\n return { ...unsigned, id: expected };\n}\n\nasync function decryptLayer(\n crypto: Nip44Decryptor,\n peer: string,\n payload: string,\n): Promise<string> {\n try {\n return await crypto.nip44Decrypt(peer, payload);\n } catch (error) {\n throw new Nip59Error(\"failed to decrypt\", { cause: error });\n }\n}\n\nexport async function unwrap(crypto: Nip44Decryptor, giftWrap: Event): Promise<Rumor> {\n const decryptor = requireNip44Decryptor(crypto);\n if (!validateSignedEvent(giftWrap) || !isGiftWrapKind(giftWrap.kind)) {\n throw new Nip59Error(\"expected gift wrap\");\n }\n if (!verifyEvent(giftWrap)) {\n throw new Nip59Error(\"gift wrap signature\");\n }\n\n const sealJson = await decryptLayer(decryptor, giftWrap.pubkey, giftWrap.content);\n const sealRaw = parseJson(sealJson);\n if (!validateSignedEvent(sealRaw)) {\n throw new Nip59Error(\"expected seal\");\n }\n if (sealRaw.kind !== Kind.Seal) {\n throw new Nip59Error(\"expected seal\");\n }\n // NIP-17: seals may carry an [\"expiration\", <unix ts>] tag for disappearing messages.\n for (const tag of sealRaw.tags) {\n if (tag[0] !== \"expiration\" || typeof tag[1] !== \"string\" || !/^\\d+$/.test(tag[1])) {\n throw new Nip59Error(\"seal tags must be empty\");\n }\n }\n if (!verifyEvent(sealRaw)) {\n throw new Nip59Error(\"seal signature\");\n }\n\n const rumorJson = await decryptLayer(decryptor, sealRaw.pubkey, sealRaw.content);\n const rumor = parseRumor(parseJson(rumorJson));\n if (sealRaw.pubkey.toLowerCase() !== rumor.pubkey.toLowerCase()) {\n throw new Nip59Error(\"seal pubkey does not match rumor pubkey\");\n }\n return rumor;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nip59.mjs","names":["TagBuilder"],"sources":["../../src/nips/nip59.ts"],"sourcesContent":["/**\n * NIP-59: Gift Wrap. Rumor → NIP-44 seal (kind 13) → gift wrap (kind 1059). Does not import signer,\n * relay, or client. Crypto is a structural type.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/59.md\n */\nimport { randomBytes } from \"@noble/hashes/utils.js\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event, UnsignedEvent } from \"../core/event.ts\";\nimport { getEventHash, validateEvent, validateSignedEvent } from \"../core/event.ts\";\nimport { Keys, finalizeEvent } from \"../core/key.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport type { Tag } from \"../core/tag.ts\";\nimport { Tag as TagBuilder } from \"../core/tag.ts\";\nimport { assertHex32, isRecord, nowSeconds } from \"../core/util.ts\";\nimport { verifyEvent } from \"../core/verifier.ts\";\nimport { encryptToPubkey } from \"./nip44.ts\";\n\n/** Unsigned event with a computed id. Never has `sig`. */\nexport type Rumor = UnsignedEvent & {\n readonly id: string;\n};\n\n/**\n * Structural crypto used by NIP-59. Satisfied by NostrSigner when nip44Encrypt/nip44Decrypt are\n * present. This module must not import src/signer/.\n */\nexport type Nip59Crypto = {\n getPublicKey: () => Promise<string>;\n signEvent: (unsigned: UnsignedEvent) => Promise<Event>;\n nip44Encrypt: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type Nip44Decryptor = {\n nip44Decrypt: (peer: string, payload: string) => Promise<string>;\n};\n\nexport type GiftWrapTimestamps = {\n readonly seal: number;\n readonly wrap: number;\n};\n\n/** `\"wrap\"` randomizes only the gift wrap; `\"seal+wrap\"` (default) randomizes both. */\nexport type TimestampRandomize = \"wrap\" | \"seal+wrap\";\n\nexport type WrapOptions = {\n /** Unix seconds used as the randomization window end. Default: floor(Date.now()/1000). */\n readonly now?: number | undefined;\n /** Uniform integer in [0, maxExclusive). Default: CSPRNG via @noble/hashes randomBytes. */\n readonly randomInt?: ((maxExclusive: number) => number) | undefined;\n /** When set, used as-is. Overrides now/randomInt for this call. */\n readonly timestamps?: GiftWrapTimestamps | undefined;\n readonly relayHint?: string | undefined;\n /** Appended after the required wrap `p` tag. Never applied to the seal. */\n readonly extraTags?: ReadonlyArray<Tag> | undefined;\n /**\n * NIP-17 disappearing-message expiry (unix seconds): sets `[\"expiration\", ts]` on the seal and on\n * the gift wrap, signalling relays to discard the wrap after that time.\n */\n readonly expiration?: number | undefined;\n /** Use ephemeral gift wrap kind 21059 instead of 1059 (NIP-59; relays MUST NOT store). */\n readonly ephemeral?: boolean | undefined;\n /**\n * Default `\"seal+wrap\"` (NIP-59). `\"wrap\"` = only wrap timestamp is randomized; seal uses\n * rumor.created_at.\n */\n readonly randomize?: TimestampRandomize | undefined;\n};\n\nexport type SealOptions = Pick<\n WrapOptions,\n \"now\" | \"randomInt\" | \"timestamps\" | \"expiration\" | \"randomize\"\n>;\n\nexport class Nip59Error extends NostrError {\n override name = \"Nip59Error\";\n}\n\nexport const TWO_DAYS_SECS: number = 2 * 24 * 60 * 60;\n\nexport function isGiftWrapKind(kind: number): boolean {\n return kind === Kind.GiftWrap || kind === Kind.GiftWrapEphemeral;\n}\n\ntype Nip59CryptoInput = {\n getPublicKey: () => Promise<string>;\n signEvent: (unsigned: UnsignedEvent) => Promise<Event>;\n nip44Encrypt?: (peer: string, plaintext: string) => Promise<string>;\n nip44Decrypt?: (peer: string, payload: string) => Promise<string>;\n};\n\n/** Narrow a signer-shaped object to {@link Nip59Crypto}, or throw. */\nexport function requireNip59Crypto(crypto: Nip59CryptoInput): Nip59Crypto {\n const encrypt = crypto.nip44Encrypt;\n const decrypt = crypto.nip44Decrypt;\n if (!encrypt || !decrypt) {\n throw new Nip59Error(\"NIP-44 is required\");\n }\n return {\n getPublicKey: async () => crypto.getPublicKey(),\n signEvent: async (unsigned) => crypto.signEvent(unsigned),\n nip44Encrypt: async (peer, plaintext) => encrypt.call(crypto, peer, plaintext),\n nip44Decrypt: async (peer, payload) => decrypt.call(crypto, peer, payload),\n };\n}\n\nexport function requireNip44Decryptor(crypto: {\n nip44Decrypt?: (peer: string, payload: string) => Promise<string>;\n}): Nip44Decryptor {\n const decrypt = crypto.nip44Decrypt;\n if (!decrypt) {\n throw new Nip59Error(\"NIP-44 is required\");\n }\n return {\n nip44Decrypt: async (peer, payload) => decrypt.call(crypto, peer, payload),\n };\n}\n\nexport function rumorToJson(rumor: Rumor): string {\n return JSON.stringify({\n id: rumor.id,\n pubkey: rumor.pubkey,\n created_at: rumor.created_at,\n kind: rumor.kind,\n tags: rumor.tags,\n content: rumor.content,\n });\n}\n\nexport function eventToJson(event: Event): string {\n return JSON.stringify({\n id: event.id,\n pubkey: event.pubkey,\n created_at: event.created_at,\n kind: event.kind,\n tags: event.tags,\n content: event.content,\n sig: event.sig,\n });\n}\n\nfunction assertExpiration(ts: number): number {\n if (!Number.isSafeInteger(ts) || ts < 0) {\n throw new Nip59Error(\"expiration must be a non-negative safe integer\");\n }\n return ts;\n}\n\nfunction defaultRandomInt(maxExclusive: number): number {\n if (!Number.isSafeInteger(maxExclusive) || maxExclusive <= 0 || maxExclusive > 0x100000000) {\n throw new Nip59Error(\"randomInt bound must be a positive integer within uint32 range\");\n }\n // rejection sampling: discard draws ≥ the largest multiple of maxExclusive that fits uint32\n const limit = Math.floor(0x100000000 / maxExclusive) * maxExclusive;\n for (;;) {\n const bytes = randomBytes(4);\n const n = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0);\n if (n < limit) {\n return n % maxExclusive;\n }\n }\n}\n\nexport function randomPastTimestamp(opts?: {\n now?: number | undefined;\n randomInt?: ((maxExclusive: number) => number) | undefined;\n}): number {\n const now = opts?.now ?? nowSeconds();\n const offset = (opts?.randomInt ?? defaultRandomInt)(TWO_DAYS_SECS);\n return now - offset;\n}\n\nexport function createRumor(\n pubkey: string,\n template: {\n kind: number;\n content?: string | undefined;\n tags?: ReadonlyArray<Tag> | undefined;\n created_at?: number | undefined;\n },\n): Rumor {\n const unsigned: UnsignedEvent = {\n kind: template.kind,\n content: template.content ?? \"\",\n tags: template.tags ? [...template.tags] : [],\n created_at: template.created_at ?? nowSeconds(),\n pubkey: assertHex32(pubkey, \"public key\"),\n };\n return { ...unsigned, id: getEventHash(unsigned) };\n}\n\nexport async function createSeal(\n crypto: Nip59Crypto,\n recipient: string,\n rumor: Rumor,\n opts?: SealOptions,\n): Promise<Event> {\n const recipientPk = assertHex32(recipient, \"public key\");\n let content: string;\n try {\n content = await crypto.nip44Encrypt(recipientPk, rumorToJson(rumor));\n } catch (error) {\n throw new Nip59Error(\"failed to encrypt\", { cause: error });\n }\n const created_at =\n opts?.timestamps?.seal ??\n (opts?.randomize === \"wrap\" ? rumor.created_at : randomPastTimestamp(opts));\n const tags: Tag[] = [];\n if (opts?.expiration !== undefined) {\n tags.push([\"expiration\", String(assertExpiration(opts.expiration))]);\n }\n const pubkey = await crypto.getPublicKey();\n return crypto.signEvent({\n kind: Kind.Seal,\n content,\n created_at,\n tags,\n pubkey,\n });\n}\n\nexport function createGiftWrap(seal: Event, recipient: string, opts?: WrapOptions): Event {\n const recipientPk = assertHex32(recipient, \"public key\");\n const ephemeral = Keys.generate();\n const content = encryptToPubkey(eventToJson(seal), ephemeral.secretKey.bytes, recipientPk);\n const created_at = opts?.timestamps?.wrap ?? randomPastTimestamp(opts);\n const tags: Tag[] = [TagBuilder.p(recipientPk, opts?.relayHint)];\n if (opts?.expiration !== undefined) {\n tags.push([\"expiration\", String(assertExpiration(opts.expiration))]);\n }\n tags.push(...(opts?.extraTags ?? []));\n return finalizeEvent(\n {\n kind: opts?.ephemeral === true ? Kind.GiftWrapEphemeral : Kind.GiftWrap,\n content,\n created_at,\n tags,\n },\n ephemeral.secretKey,\n );\n}\n\nexport async function wrap(\n crypto: Nip59Crypto,\n recipient: string,\n rumor: Rumor,\n opts?: WrapOptions,\n): Promise<Event> {\n const seal = await createSeal(crypto, recipient, rumor, opts);\n return createGiftWrap(seal, recipient, opts);\n}\n\nfunction parseJson(text: string): unknown {\n try {\n return JSON.parse(text);\n } catch (error) {\n throw new Nip59Error(\"invalid JSON\", { cause: error });\n }\n}\n\nfunction parseRumor(value: unknown): Rumor {\n if (!isRecord(value)) {\n throw new Nip59Error(\"invalid rumor\");\n }\n if (Object.hasOwn(value, \"sig\")) {\n throw new Nip59Error(\"rumor must be unsigned\");\n }\n const givenId = value[\"id\"];\n if (!validateEvent(value)) {\n throw new Nip59Error(\"invalid rumor\");\n }\n const unsigned: UnsignedEvent = {\n kind: value.kind,\n tags: value.tags,\n content: value.content,\n created_at: value.created_at,\n pubkey: value.pubkey.toLowerCase(),\n };\n const expected = getEventHash(unsigned);\n if (typeof givenId === \"string\" && givenId.toLowerCase() !== expected) {\n throw new Nip59Error(\"invalid rumor\");\n }\n return { ...unsigned, id: expected };\n}\n\nasync function decryptLayer(\n crypto: Nip44Decryptor,\n peer: string,\n payload: string,\n): Promise<string> {\n try {\n return await crypto.nip44Decrypt(peer, payload);\n } catch (error) {\n throw new Nip59Error(\"failed to decrypt\", { cause: error });\n }\n}\n\nexport async function unwrap(crypto: Nip44Decryptor, giftWrap: Event): Promise<Rumor> {\n const decryptor = requireNip44Decryptor(crypto);\n if (!validateSignedEvent(giftWrap) || !isGiftWrapKind(giftWrap.kind)) {\n throw new Nip59Error(\"expected gift wrap\");\n }\n if (!verifyEvent(giftWrap)) {\n throw new Nip59Error(\"gift wrap signature\");\n }\n\n const sealJson = await decryptLayer(decryptor, giftWrap.pubkey, giftWrap.content);\n const sealRaw = parseJson(sealJson);\n if (!validateSignedEvent(sealRaw)) {\n throw new Nip59Error(\"expected seal\");\n }\n if (sealRaw.kind !== Kind.Seal) {\n throw new Nip59Error(\"expected seal\");\n }\n // NIP-17: seals may carry an [\"expiration\", <unix ts>] tag for disappearing messages.\n for (const tag of sealRaw.tags) {\n if (tag[0] !== \"expiration\" || typeof tag[1] !== \"string\" || !/^\\d+$/.test(tag[1])) {\n throw new Nip59Error(\"seal tags must be empty\");\n }\n }\n if (!verifyEvent(sealRaw)) {\n throw new Nip59Error(\"seal signature\");\n }\n\n const rumorJson = await decryptLayer(decryptor, sealRaw.pubkey, sealRaw.content);\n const rumor = parseRumor(parseJson(rumorJson));\n if (sealRaw.pubkey.toLowerCase() !== rumor.pubkey.toLowerCase()) {\n throw new Nip59Error(\"seal pubkey does not match rumor pubkey\");\n }\n return rumor;\n}\n"],"mappings":"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"}
|
package/dist/nips/nip65.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as relayListEventBuilder, i as readRelays, n as RelayMarker, o as relayListToTags, r as parseRelayList, s as writeRelays, t as RelayListItem } from "../nip65-
|
|
1
|
+
import { a as relayListEventBuilder, i as readRelays, n as RelayMarker, o as relayListToTags, r as parseRelayList, s as writeRelays, t as RelayListItem } from "../nip65-D1fbmhOg.mjs";
|
|
2
2
|
export { RelayListItem, RelayMarker, parseRelayList, readRelays, relayListEventBuilder, relayListToTags, writeRelays };
|
package/dist/nips/nip65.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { h as EventValidationError, u as normalizeURL } from "../util-
|
|
1
|
+
import { h as EventValidationError, u as normalizeURL } from "../util-DMF9AIO0.mjs";
|
|
2
2
|
import { t as Kind } from "../kind-Cxwhhkx8.mjs";
|
|
3
|
-
import { t as EventBuilder } from "../builder-
|
|
3
|
+
import { t as EventBuilder } from "../builder-aNU2gGFP.mjs";
|
|
4
4
|
//#region src/nips/nip65.ts
|
|
5
5
|
/** Parse a kind:10002 NIP-65 event into relay list entries. */
|
|
6
6
|
function parseRelayList(event) {
|
package/dist/nips/nip77.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as NegentropyStorageVector, c as ReconcileOutcome, d as storageFromItems, i as Negentropy, l as runNegSession, n as MAX_NEG_ROUNDS, o as Nip77Error, r as NegItem, s as PROTOCOL_VERSION, t as DEFAULT_FRAME_SIZE_LIMIT, u as storageFromEvents } from "../nip77-
|
|
1
|
+
import { a as NegentropyStorageVector, c as ReconcileOutcome, d as storageFromItems, i as Negentropy, l as runNegSession, n as MAX_NEG_ROUNDS, o as Nip77Error, r as NegItem, s as PROTOCOL_VERSION, t as DEFAULT_FRAME_SIZE_LIMIT, u as storageFromEvents } from "../nip77-DzUwUxHq.mjs";
|
|
2
2
|
export { DEFAULT_FRAME_SIZE_LIMIT, MAX_NEG_ROUNDS, NegItem, Negentropy, NegentropyStorageVector, Nip77Error, PROTOCOL_VERSION, ReconcileOutcome, runNegSession, storageFromEvents, storageFromItems };
|
package/dist/nips/nip77.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as hexToBytes, i as bytesToHex, n as assertHex32, v as NostrError } from "../util-
|
|
1
|
+
import { a as hexToBytes, i as bytesToHex, n as assertHex32, v as NostrError } from "../util-DMF9AIO0.mjs";
|
|
2
2
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
3
3
|
//#region src/nips/nip77.ts
|
|
4
4
|
/**
|
package/dist/nips/nip77.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nip77.mjs","names":[],"sources":["../../src/nips/nip77.ts"],"sourcesContent":["/**\n * NIP-77: Negentropy Syncing. Transport-free V1 algorithm. Does not import relay or client.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/77.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { assertHex32, bytesToHex, hexToBytes } from \"../core/util.ts\";\n\nexport const PROTOCOL_VERSION = 0x61;\nexport const DEFAULT_FRAME_SIZE_LIMIT = 60_000;\nexport const MAX_NEG_ROUNDS = 1024;\n\nconst ID_SIZE = 32;\nconst FINGERPRINT_SIZE = 16;\nconst INFINITY = Number.MAX_VALUE;\nconst BUCKETS = 16;\nconst ID_LIST_THRESHOLD = BUCKETS * 2;\n\nconst Mode = {\n Skip: 0,\n Fingerprint: 1,\n IdList: 2,\n} as const;\n\nexport class Nip77Error extends NostrError {\n override name = \"Nip77Error\";\n}\n\nexport type NegItem = {\n timestamp: number;\n id: Uint8Array;\n};\n\nexport type ReconcileOutcome = {\n /** Event ids the local side holds that the peer does not. */\n have: string[];\n /** Event ids the peer holds that the local side does not. */\n need: string[];\n /** Next NEG-MSG hex, or undefined when the session converged. */\n nextMessage: string | undefined;\n};\n\n/**\n * Byte buffer that is either an append target (`extend`/`unwrap`, `#pos` stays 0) or a read cursor\n * (`read`/`readN` advance `#pos`). `length` is bytes remaining ahead of `#pos`.\n */\nclass EncodedBuf {\n #raw: Uint8Array;\n #pos = 0;\n #end = 0;\n\n constructor(buffer?: Uint8Array | number) {\n if (typeof buffer === \"number\") {\n this.#raw = new Uint8Array(buffer);\n } else if (buffer) {\n this.#raw = new Uint8Array(buffer);\n this.#end = buffer.length;\n } else {\n this.#raw = new Uint8Array(512);\n }\n }\n\n get length(): number {\n return this.#end - this.#pos;\n }\n\n /** Unread (or written) bytes — never a live view for appends after reads. */\n unwrap(): Uint8Array {\n return this.#raw.subarray(this.#pos, this.#end);\n }\n\n get capacity(): number {\n return this.#raw.byteLength;\n }\n\n extend(buf: Uint8Array | EncodedBuf): void {\n const bytes = buf instanceof EncodedBuf ? buf.unwrap() : buf;\n const targetSize = bytes.length + this.#end;\n if (this.capacity < targetSize) {\n const old = this.#raw;\n const next = new Uint8Array(Math.max(this.capacity * 2, targetSize));\n next.set(old);\n this.#raw = next;\n }\n this.#raw.set(bytes, this.#end);\n this.#end += bytes.length;\n }\n\n read(): number {\n if (this.length === 0) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const first = this.#raw[this.#pos];\n if (first === undefined) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n this.#pos += 1;\n return first;\n }\n\n readN(n: number): Uint8Array {\n if (this.length < n) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const head = this.#raw.subarray(this.#pos, this.#pos + n);\n this.#pos += n;\n return head;\n }\n}\n\nfunction decodeVarInt(buf: EncodedBuf): number {\n let res = 0;\n for (;;) {\n const byte = buf.read();\n res = res * 128 + (byte & 0x7f);\n if (res > Number.MAX_SAFE_INTEGER) {\n throw new Nip77Error(\"varint exceeds max safe integer\");\n }\n if ((byte & 0x80) === 0) {\n break;\n }\n }\n return res;\n}\n\nfunction encodeVarInt(n: number): EncodedBuf {\n if (!Number.isSafeInteger(n) || n < 0) {\n throw new Nip77Error(\"varint must be a non-negative safe integer\");\n }\n if (n === 0) {\n return new EncodedBuf(new Uint8Array([0]));\n }\n const digits: number[] = [];\n let value = n;\n while (value !== 0) {\n digits.push(value % 128);\n value = Math.floor(value / 128);\n }\n digits.reverse();\n const encoded = digits.map((digit, i) => (i === digits.length - 1 ? digit : digit | 0x80));\n return new EncodedBuf(new Uint8Array(encoded));\n}\n\nfunction getBytes(buf: EncodedBuf, n: number): Uint8Array {\n return buf.readN(n);\n}\n\nclass Accumulator {\n readonly #buf: Uint8Array;\n\n constructor() {\n this.#buf = new Uint8Array(ID_SIZE);\n }\n\n add(other: Uint8Array): void {\n const view = new DataView(this.#buf.buffer, this.#buf.byteOffset, this.#buf.byteLength);\n const otherView = new DataView(other.buffer, other.byteOffset, other.byteLength);\n let carry = 0;\n for (let i = 0; i < 8; i++) {\n const offset = i * 4;\n const next = view.getUint32(offset, true) + carry + otherView.getUint32(offset, true);\n carry = next > 0xffffffff ? 1 : 0;\n view.setUint32(offset, next >>> 0, true);\n }\n }\n\n fingerprint(n: number): Uint8Array {\n const input = new EncodedBuf();\n input.extend(this.#buf);\n input.extend(encodeVarInt(n));\n return sha256(input.unwrap()).subarray(0, FINGERPRINT_SIZE);\n }\n}\n\nfunction compareBytes(a: Uint8Array, b: Uint8Array): number {\n const n = Math.min(a.byteLength, b.byteLength);\n for (let i = 0; i < n; i++) {\n const x = a[i];\n const y = b[i];\n if (x === undefined || y === undefined) {\n break;\n }\n if (x < y) {\n return -1;\n }\n if (x > y) {\n return 1;\n }\n }\n if (a.byteLength > b.byteLength) {\n return 1;\n }\n if (a.byteLength < b.byteLength) {\n return -1;\n }\n return 0;\n}\n\nfunction compareNegItems(a: NegItem, b: NegItem): number {\n if (a.timestamp !== b.timestamp) {\n return a.timestamp - b.timestamp;\n }\n return compareBytes(a.id, b.id);\n}\n\nexport class NegentropyStorageVector {\n readonly #items: NegItem[] = [];\n #sealed = false;\n\n insert(timestamp: number, id: string): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n if (timestamp === INFINITY) {\n throw new Nip77Error(\"timestamp is reserved infinity\");\n }\n const idb = hexToBytes(assertHex32(id, \"event id\"));\n this.#items.push({ timestamp, id: idb });\n }\n\n seal(): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n this.#sealed = true;\n this.#items.sort(compareNegItems);\n for (let i = 1; i < this.#items.length; i++) {\n const prev = this.#items[i - 1];\n const item = this.#items[i];\n if (prev !== undefined && item !== undefined && compareNegItems(prev, item) === 0) {\n throw new Nip77Error(\"duplicate item inserted\");\n }\n }\n }\n\n size(): number {\n this.#checkSealed();\n return this.#items.length;\n }\n\n iterate(begin: number, end: number, cb: (item: NegItem, i: number) => boolean): void {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n for (let i = begin; i < end; i++) {\n const item = this.#items[i];\n if (item === undefined || !cb(item, i)) {\n break;\n }\n }\n }\n\n findLowerBound(begin: number, end: number, bound: NegItem): number {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n let first = begin;\n let count = end - begin;\n while (count > 0) {\n const step = Math.floor(count / 2);\n const it = first + step;\n const item = this.#items[it];\n if (item === undefined) {\n break;\n }\n if (compareNegItems(item, bound) < 0) {\n first = it + 1;\n count -= step + 1;\n } else {\n count = step;\n }\n }\n return first;\n }\n\n fingerprint(begin: number, end: number): Uint8Array {\n const acc = new Accumulator();\n this.iterate(begin, end, (item) => {\n acc.add(item.id);\n return true;\n });\n return acc.fingerprint(end - begin);\n }\n\n #checkSealed(): void {\n if (!this.#sealed) {\n throw new Nip77Error(\"not sealed\");\n }\n }\n\n #checkBounds(begin: number, end: number): void {\n if (begin > end || end > this.#items.length) {\n throw new Nip77Error(\"bad range\");\n }\n }\n}\n\nexport function storageFromItems(\n items: ReadonlyArray<{ id: string; created_at: number }>,\n): NegentropyStorageVector {\n const storage = new NegentropyStorageVector();\n const seen = new Set<string>();\n for (const item of items) {\n const id = item.id.toLowerCase();\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n storage.insert(item.created_at, id);\n }\n storage.seal();\n return storage;\n}\n\nexport function storageFromEvents(\n events: ReadonlyArray<Pick<Event, \"id\" | \"created_at\">>,\n): NegentropyStorageVector {\n return storageFromItems(events);\n}\n\ntype Bound = { timestamp: number; id: Uint8Array };\n\nexport class Negentropy {\n readonly #storage: NegentropyStorageVector;\n readonly #frameSizeLimit: number;\n #initiator = false;\n #lastTimestampIn = 0;\n #lastTimestampOut = 0;\n\n constructor(storage: NegentropyStorageVector, frameSizeLimit: number = DEFAULT_FRAME_SIZE_LIMIT) {\n if (frameSizeLimit !== 0 && frameSizeLimit < 4096) {\n throw new Nip77Error(\"frameSizeLimit too small\");\n }\n this.#storage = storage;\n this.#frameSizeLimit = frameSizeLimit;\n }\n\n initiate(): string {\n this.#initiator = true;\n const output = new EncodedBuf();\n output.extend(new Uint8Array([PROTOCOL_VERSION]));\n this.#splitRange(0, this.#storage.size(), this.#bound(INFINITY), output);\n return bytesToHex(output.unwrap());\n }\n\n reconcile(queryMsg: string): ReconcileOutcome {\n const have: string[] = [];\n const need: string[] = [];\n const query = new EncodedBuf(hexToBytes(queryMsg));\n\n this.#lastTimestampIn = 0;\n this.#lastTimestampOut = 0;\n\n const fullOutput = new EncodedBuf();\n fullOutput.extend(new Uint8Array([PROTOCOL_VERSION]));\n\n const protocolVersion = query.read();\n if (protocolVersion < 0x60 || protocolVersion > 0x6f) {\n throw new Nip77Error(\"invalid negentropy protocol version byte\");\n }\n if (protocolVersion !== PROTOCOL_VERSION) {\n // NIP-77: reply with the highest supported version so the peer can downgrade.\n return { have, need, nextMessage: bytesToHex(fullOutput.unwrap()) };\n }\n\n const storageSize = this.#storage.size();\n let prevBound = this.#bound(0);\n let prevIndex = 0;\n let skip = false;\n\n while (query.length > 0) {\n const o = new EncodedBuf();\n\n const currBound = this.#decodeBound(query);\n const mode = decodeVarInt(query);\n const lower = prevIndex;\n const upper = this.#storage.findLowerBound(prevIndex, storageSize, currBound);\n\n if (mode === Mode.Skip) {\n skip = true;\n } else if (mode === Mode.Fingerprint) {\n const theirFingerprint = getBytes(query, FINGERPRINT_SIZE);\n const ourFingerprint = this.#storage.fingerprint(lower, upper);\n if (compareBytes(theirFingerprint, ourFingerprint) === 0) {\n skip = true;\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n this.#splitRange(lower, upper, currBound, o);\n }\n } else if (mode === Mode.IdList) {\n const numIds = decodeVarInt(query);\n const theirElems = new Map<string, Uint8Array>();\n for (let i = 0; i < numIds; i++) {\n const e = getBytes(query, ID_SIZE);\n theirElems.set(bytesToHex(e), e);\n }\n\n if (this.#initiator) {\n skip = true;\n this.#storage.iterate(lower, upper, (item) => {\n const id = bytesToHex(item.id);\n if (theirElems.has(id)) {\n theirElems.delete(id);\n } else {\n have.push(id);\n }\n return true;\n });\n for (const id of theirElems.keys()) {\n need.push(id);\n }\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n o.extend(this.#encodeBound(currBound));\n o.extend(encodeVarInt(Mode.IdList));\n const ourIds: Uint8Array[] = [];\n this.#storage.iterate(lower, upper, (item) => {\n ourIds.push(item.id);\n return true;\n });\n o.extend(encodeVarInt(ourIds.length));\n for (const id of ourIds) {\n o.extend(id);\n }\n }\n } else {\n throw new Nip77Error(\"unexpected mode\");\n }\n\n if (this.#exceededFrameSizeLimit(fullOutput.length + o.length)) {\n const remainingFingerprint = this.#storage.fingerprint(upper, storageSize);\n fullOutput.extend(this.#encodeBound(this.#bound(INFINITY)));\n fullOutput.extend(encodeVarInt(Mode.Fingerprint));\n fullOutput.extend(remainingFingerprint);\n break;\n }\n fullOutput.extend(o);\n prevIndex = upper;\n prevBound = currBound;\n }\n\n return {\n have,\n need,\n nextMessage: fullOutput.length === 1 ? undefined : bytesToHex(fullOutput.unwrap()),\n };\n }\n\n #bound(timestamp: number, id?: Uint8Array): Bound {\n return { timestamp, id: id ?? new Uint8Array(0) };\n }\n\n #splitRange(lower: number, upper: number, upperBound: Bound, o: EncodedBuf): void {\n const numElems = upper - lower;\n if (numElems < ID_LIST_THRESHOLD) {\n o.extend(this.#encodeBound(upperBound));\n o.extend(encodeVarInt(Mode.IdList));\n o.extend(encodeVarInt(numElems));\n this.#storage.iterate(lower, upper, (item) => {\n o.extend(item.id);\n return true;\n });\n return;\n }\n\n const itemsPerBucket = Math.floor(numElems / BUCKETS);\n const bucketsWithExtra = numElems % BUCKETS;\n let curr = lower;\n for (let i = 0; i < BUCKETS; i++) {\n const bucketSize = itemsPerBucket + (i < bucketsWithExtra ? 1 : 0);\n const ourFingerprint = this.#storage.fingerprint(curr, curr + bucketSize);\n curr += bucketSize;\n let nextBound: Bound;\n if (curr === upper) {\n nextBound = upperBound;\n } else {\n const at = curr - 1;\n let prevItem: NegItem | undefined;\n let currItem: NegItem | undefined;\n this.#storage.iterate(at, at + 2, (item, index) => {\n if (index === at) {\n prevItem = item;\n } else {\n currItem = item;\n }\n return true;\n });\n if (prevItem === undefined || currItem === undefined) {\n throw new Nip77Error(\"missing bound items\");\n }\n nextBound = this.#minimalBound(prevItem, currItem);\n }\n o.extend(this.#encodeBound(nextBound));\n o.extend(encodeVarInt(Mode.Fingerprint));\n o.extend(ourFingerprint);\n }\n }\n\n #exceededFrameSizeLimit(n: number): boolean {\n if (this.#frameSizeLimit === 0) {\n return false;\n }\n return n > this.#frameSizeLimit - 200;\n }\n\n #decodeTimestampIn(encoded: EncodedBuf): number {\n let timestamp = decodeVarInt(encoded);\n timestamp = timestamp === 0 ? INFINITY : timestamp - 1;\n if (this.#lastTimestampIn === INFINITY || timestamp === INFINITY) {\n this.#lastTimestampIn = INFINITY;\n return INFINITY;\n }\n timestamp += this.#lastTimestampIn;\n this.#lastTimestampIn = timestamp;\n return timestamp;\n }\n\n #decodeBound(encoded: EncodedBuf): Bound {\n const timestamp = this.#decodeTimestampIn(encoded);\n const len = decodeVarInt(encoded);\n if (len > ID_SIZE) {\n throw new Nip77Error(\"bound key too long\");\n }\n return { timestamp, id: getBytes(encoded, len) };\n }\n\n #encodeTimestampOut(timestamp: number): EncodedBuf {\n if (timestamp === INFINITY) {\n this.#lastTimestampOut = INFINITY;\n return encodeVarInt(0);\n }\n const delta = timestamp - this.#lastTimestampOut;\n this.#lastTimestampOut = timestamp;\n return encodeVarInt(delta + 1);\n }\n\n #encodeSkipInto(o: EncodedBuf, bound: Bound): void {\n o.extend(this.#encodeBound(bound));\n o.extend(encodeVarInt(Mode.Skip));\n }\n\n #encodeBound(key: Bound): EncodedBuf {\n const output = new EncodedBuf();\n output.extend(this.#encodeTimestampOut(key.timestamp));\n output.extend(encodeVarInt(key.id.length));\n output.extend(key.id);\n return output;\n }\n\n #minimalBound(prev: NegItem, curr: NegItem): Bound {\n if (curr.timestamp !== prev.timestamp) {\n return this.#bound(curr.timestamp);\n }\n let shared = 0;\n for (let i = 0; i < ID_SIZE; i++) {\n if (curr.id[i] !== prev.id[i]) {\n break;\n }\n shared += 1;\n }\n return this.#bound(curr.timestamp, curr.id.subarray(0, shared + 1));\n }\n}\n\n/** Drive initiate/reconcile until nextMessage is null. Transport-free. */\nexport async function runNegSession(opts: {\n storage: NegentropyStorageVector;\n frameSizeLimit?: number;\n openingSend: (hex: string) => void;\n msgSend: (hex: string) => void;\n next: () => Promise<string>;\n}): Promise<{ have: string[]; need: string[] }> {\n const neg = new Negentropy(opts.storage, opts.frameSizeLimit);\n const have = new Set<string>();\n const need = new Set<string>();\n opts.openingSend(neg.initiate());\n for (let round = 0; round < MAX_NEG_ROUNDS; round++) {\n // oxlint-disable-next-line no-await-in-loop -- protocol rounds are sequential by definition\n const out = neg.reconcile(await opts.next());\n for (const id of out.have) {\n have.add(id);\n }\n for (const id of out.need) {\n need.add(id);\n }\n if (out.nextMessage === undefined) {\n return { have: [...have], need: [...need] };\n }\n opts.msgSend(out.nextMessage);\n }\n throw new Nip77Error(\"negentropy exceeded max rounds\");\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"nip77.mjs","names":[],"sources":["../../src/nips/nip77.ts"],"sourcesContent":["/**\n * NIP-77: Negentropy Syncing. Transport-free V1 algorithm. Does not import relay or client.\n *\n * @see https://github.com/nostr-protocol/nips/blob/master/77.md\n */\nimport { sha256 } from \"@noble/hashes/sha2.js\";\n\nimport { NostrError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { assertHex32, bytesToHex, hexToBytes } from \"../core/util.ts\";\n\nexport const PROTOCOL_VERSION = 0x61;\nexport const DEFAULT_FRAME_SIZE_LIMIT = 60_000;\nexport const MAX_NEG_ROUNDS = 1024;\n\nconst ID_SIZE = 32;\nconst FINGERPRINT_SIZE = 16;\nconst INFINITY = Number.MAX_VALUE;\nconst BUCKETS = 16;\nconst ID_LIST_THRESHOLD = BUCKETS * 2;\n\nconst Mode = {\n Skip: 0,\n Fingerprint: 1,\n IdList: 2,\n} as const;\n\nexport class Nip77Error extends NostrError {\n override name = \"Nip77Error\";\n}\n\nexport type NegItem = {\n readonly timestamp: number;\n readonly id: Uint8Array;\n};\n\nexport type ReconcileOutcome = {\n /** Event ids the local side holds that the peer does not. */\n readonly have: ReadonlyArray<string>;\n /** Event ids the peer holds that the local side does not. */\n readonly need: ReadonlyArray<string>;\n /** Next NEG-MSG hex, or undefined when the session converged. */\n readonly nextMessage: string | undefined;\n};\n\n/**\n * Byte buffer that is either an append target (`extend`/`unwrap`, `#pos` stays 0) or a read cursor\n * (`read`/`readN` advance `#pos`). `length` is bytes remaining ahead of `#pos`.\n */\nclass EncodedBuf {\n #raw: Uint8Array;\n #pos = 0;\n #end = 0;\n\n constructor(buffer?: Uint8Array | number) {\n if (typeof buffer === \"number\") {\n this.#raw = new Uint8Array(buffer);\n } else if (buffer) {\n this.#raw = new Uint8Array(buffer);\n this.#end = buffer.length;\n } else {\n this.#raw = new Uint8Array(512);\n }\n }\n\n get length(): number {\n return this.#end - this.#pos;\n }\n\n /** Unread (or written) bytes — never a live view for appends after reads. */\n unwrap(): Uint8Array {\n return this.#raw.subarray(this.#pos, this.#end);\n }\n\n get capacity(): number {\n return this.#raw.byteLength;\n }\n\n extend(buf: Uint8Array | EncodedBuf): void {\n const bytes = buf instanceof EncodedBuf ? buf.unwrap() : buf;\n const targetSize = bytes.length + this.#end;\n if (this.capacity < targetSize) {\n const old = this.#raw;\n const next = new Uint8Array(Math.max(this.capacity * 2, targetSize));\n next.set(old);\n this.#raw = next;\n }\n this.#raw.set(bytes, this.#end);\n this.#end += bytes.length;\n }\n\n read(): number {\n if (this.length === 0) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const first = this.#raw[this.#pos];\n if (first === undefined) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n this.#pos += 1;\n return first;\n }\n\n readN(n: number): Uint8Array {\n if (this.length < n) {\n throw new Nip77Error(\"parse ends prematurely\");\n }\n const head = this.#raw.subarray(this.#pos, this.#pos + n);\n this.#pos += n;\n return head;\n }\n}\n\nfunction decodeVarInt(buf: EncodedBuf): number {\n let res = 0;\n for (;;) {\n const byte = buf.read();\n res = res * 128 + (byte & 0x7f);\n if (res > Number.MAX_SAFE_INTEGER) {\n throw new Nip77Error(\"varint exceeds max safe integer\");\n }\n if ((byte & 0x80) === 0) {\n break;\n }\n }\n return res;\n}\n\nfunction encodeVarInt(n: number): EncodedBuf {\n if (!Number.isSafeInteger(n) || n < 0) {\n throw new Nip77Error(\"varint must be a non-negative safe integer\");\n }\n if (n === 0) {\n return new EncodedBuf(new Uint8Array([0]));\n }\n const digits: number[] = [];\n let value = n;\n while (value !== 0) {\n digits.push(value % 128);\n value = Math.floor(value / 128);\n }\n digits.reverse();\n const encoded = digits.map((digit, i) => (i === digits.length - 1 ? digit : digit | 0x80));\n return new EncodedBuf(new Uint8Array(encoded));\n}\n\nfunction getBytes(buf: EncodedBuf, n: number): Uint8Array {\n return buf.readN(n);\n}\n\nclass Accumulator {\n readonly #buf: Uint8Array;\n\n constructor() {\n this.#buf = new Uint8Array(ID_SIZE);\n }\n\n add(other: Uint8Array): void {\n const view = new DataView(this.#buf.buffer, this.#buf.byteOffset, this.#buf.byteLength);\n const otherView = new DataView(other.buffer, other.byteOffset, other.byteLength);\n let carry = 0;\n for (let i = 0; i < 8; i++) {\n const offset = i * 4;\n const next = view.getUint32(offset, true) + carry + otherView.getUint32(offset, true);\n carry = next > 0xffffffff ? 1 : 0;\n view.setUint32(offset, next >>> 0, true);\n }\n }\n\n fingerprint(n: number): Uint8Array {\n const input = new EncodedBuf();\n input.extend(this.#buf);\n input.extend(encodeVarInt(n));\n return sha256(input.unwrap()).subarray(0, FINGERPRINT_SIZE);\n }\n}\n\nfunction compareBytes(a: Uint8Array, b: Uint8Array): number {\n const n = Math.min(a.byteLength, b.byteLength);\n for (let i = 0; i < n; i++) {\n const x = a[i];\n const y = b[i];\n if (x === undefined || y === undefined) {\n break;\n }\n if (x < y) {\n return -1;\n }\n if (x > y) {\n return 1;\n }\n }\n if (a.byteLength > b.byteLength) {\n return 1;\n }\n if (a.byteLength < b.byteLength) {\n return -1;\n }\n return 0;\n}\n\nfunction compareNegItems(a: NegItem, b: NegItem): number {\n if (a.timestamp !== b.timestamp) {\n return a.timestamp - b.timestamp;\n }\n return compareBytes(a.id, b.id);\n}\n\nexport class NegentropyStorageVector {\n readonly #items: NegItem[] = [];\n #sealed = false;\n\n insert(timestamp: number, id: string): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n if (timestamp === INFINITY) {\n throw new Nip77Error(\"timestamp is reserved infinity\");\n }\n const idb = hexToBytes(assertHex32(id, \"event id\"));\n this.#items.push({ timestamp, id: idb });\n }\n\n seal(): void {\n if (this.#sealed) {\n throw new Nip77Error(\"already sealed\");\n }\n this.#sealed = true;\n this.#items.sort(compareNegItems);\n for (let i = 1; i < this.#items.length; i++) {\n const prev = this.#items[i - 1];\n const item = this.#items[i];\n if (prev !== undefined && item !== undefined && compareNegItems(prev, item) === 0) {\n throw new Nip77Error(\"duplicate item inserted\");\n }\n }\n }\n\n size(): number {\n this.#checkSealed();\n return this.#items.length;\n }\n\n iterate(begin: number, end: number, cb: (item: NegItem, i: number) => boolean): void {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n for (let i = begin; i < end; i++) {\n const item = this.#items[i];\n if (item === undefined || !cb(item, i)) {\n break;\n }\n }\n }\n\n findLowerBound(begin: number, end: number, bound: NegItem): number {\n this.#checkSealed();\n this.#checkBounds(begin, end);\n let first = begin;\n let count = end - begin;\n while (count > 0) {\n const step = Math.floor(count / 2);\n const it = first + step;\n const item = this.#items[it];\n if (item === undefined) {\n break;\n }\n if (compareNegItems(item, bound) < 0) {\n first = it + 1;\n count -= step + 1;\n } else {\n count = step;\n }\n }\n return first;\n }\n\n fingerprint(begin: number, end: number): Uint8Array {\n const acc = new Accumulator();\n this.iterate(begin, end, (item) => {\n acc.add(item.id);\n return true;\n });\n return acc.fingerprint(end - begin);\n }\n\n #checkSealed(): void {\n if (!this.#sealed) {\n throw new Nip77Error(\"not sealed\");\n }\n }\n\n #checkBounds(begin: number, end: number): void {\n if (begin > end || end > this.#items.length) {\n throw new Nip77Error(\"bad range\");\n }\n }\n}\n\nexport function storageFromItems(\n items: ReadonlyArray<{ id: string; created_at: number }>,\n): NegentropyStorageVector {\n const storage = new NegentropyStorageVector();\n const seen = new Set<string>();\n for (const item of items) {\n const id = item.id.toLowerCase();\n if (seen.has(id)) {\n continue;\n }\n seen.add(id);\n storage.insert(item.created_at, id);\n }\n storage.seal();\n return storage;\n}\n\nexport function storageFromEvents(\n events: ReadonlyArray<Pick<Event, \"id\" | \"created_at\">>,\n): NegentropyStorageVector {\n return storageFromItems(events);\n}\n\ntype Bound = { timestamp: number; id: Uint8Array };\n\nexport class Negentropy {\n readonly #storage: NegentropyStorageVector;\n readonly #frameSizeLimit: number;\n #initiator = false;\n #lastTimestampIn = 0;\n #lastTimestampOut = 0;\n\n constructor(storage: NegentropyStorageVector, frameSizeLimit: number = DEFAULT_FRAME_SIZE_LIMIT) {\n if (frameSizeLimit !== 0 && frameSizeLimit < 4096) {\n throw new Nip77Error(\"frameSizeLimit too small\");\n }\n this.#storage = storage;\n this.#frameSizeLimit = frameSizeLimit;\n }\n\n initiate(): string {\n this.#initiator = true;\n const output = new EncodedBuf();\n output.extend(new Uint8Array([PROTOCOL_VERSION]));\n this.#splitRange(0, this.#storage.size(), this.#bound(INFINITY), output);\n return bytesToHex(output.unwrap());\n }\n\n reconcile(queryMsg: string): ReconcileOutcome {\n const have: string[] = [];\n const need: string[] = [];\n const query = new EncodedBuf(hexToBytes(queryMsg));\n\n this.#lastTimestampIn = 0;\n this.#lastTimestampOut = 0;\n\n const fullOutput = new EncodedBuf();\n fullOutput.extend(new Uint8Array([PROTOCOL_VERSION]));\n\n const protocolVersion = query.read();\n if (protocolVersion < 0x60 || protocolVersion > 0x6f) {\n throw new Nip77Error(\"invalid negentropy protocol version byte\");\n }\n if (protocolVersion !== PROTOCOL_VERSION) {\n // NIP-77: reply with the highest supported version so the peer can downgrade.\n return { have, need, nextMessage: bytesToHex(fullOutput.unwrap()) };\n }\n\n const storageSize = this.#storage.size();\n let prevBound = this.#bound(0);\n let prevIndex = 0;\n let skip = false;\n\n while (query.length > 0) {\n const o = new EncodedBuf();\n\n const currBound = this.#decodeBound(query);\n const mode = decodeVarInt(query);\n const lower = prevIndex;\n const upper = this.#storage.findLowerBound(prevIndex, storageSize, currBound);\n\n if (mode === Mode.Skip) {\n skip = true;\n } else if (mode === Mode.Fingerprint) {\n const theirFingerprint = getBytes(query, FINGERPRINT_SIZE);\n const ourFingerprint = this.#storage.fingerprint(lower, upper);\n if (compareBytes(theirFingerprint, ourFingerprint) === 0) {\n skip = true;\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n this.#splitRange(lower, upper, currBound, o);\n }\n } else if (mode === Mode.IdList) {\n const numIds = decodeVarInt(query);\n const theirElems = new Map<string, Uint8Array>();\n for (let i = 0; i < numIds; i++) {\n const e = getBytes(query, ID_SIZE);\n theirElems.set(bytesToHex(e), e);\n }\n\n if (this.#initiator) {\n skip = true;\n this.#storage.iterate(lower, upper, (item) => {\n const id = bytesToHex(item.id);\n if (theirElems.has(id)) {\n theirElems.delete(id);\n } else {\n have.push(id);\n }\n return true;\n });\n for (const id of theirElems.keys()) {\n need.push(id);\n }\n } else {\n if (skip) {\n skip = false;\n this.#encodeSkipInto(o, prevBound);\n }\n o.extend(this.#encodeBound(currBound));\n o.extend(encodeVarInt(Mode.IdList));\n const ourIds: Uint8Array[] = [];\n this.#storage.iterate(lower, upper, (item) => {\n ourIds.push(item.id);\n return true;\n });\n o.extend(encodeVarInt(ourIds.length));\n for (const id of ourIds) {\n o.extend(id);\n }\n }\n } else {\n throw new Nip77Error(\"unexpected mode\");\n }\n\n if (this.#exceededFrameSizeLimit(fullOutput.length + o.length)) {\n const remainingFingerprint = this.#storage.fingerprint(upper, storageSize);\n fullOutput.extend(this.#encodeBound(this.#bound(INFINITY)));\n fullOutput.extend(encodeVarInt(Mode.Fingerprint));\n fullOutput.extend(remainingFingerprint);\n break;\n }\n fullOutput.extend(o);\n prevIndex = upper;\n prevBound = currBound;\n }\n\n return {\n have,\n need,\n nextMessage: fullOutput.length === 1 ? undefined : bytesToHex(fullOutput.unwrap()),\n };\n }\n\n #bound(timestamp: number, id?: Uint8Array): Bound {\n return { timestamp, id: id ?? new Uint8Array(0) };\n }\n\n #splitRange(lower: number, upper: number, upperBound: Bound, o: EncodedBuf): void {\n const numElems = upper - lower;\n if (numElems < ID_LIST_THRESHOLD) {\n o.extend(this.#encodeBound(upperBound));\n o.extend(encodeVarInt(Mode.IdList));\n o.extend(encodeVarInt(numElems));\n this.#storage.iterate(lower, upper, (item) => {\n o.extend(item.id);\n return true;\n });\n return;\n }\n\n const itemsPerBucket = Math.floor(numElems / BUCKETS);\n const bucketsWithExtra = numElems % BUCKETS;\n let curr = lower;\n for (let i = 0; i < BUCKETS; i++) {\n const bucketSize = itemsPerBucket + (i < bucketsWithExtra ? 1 : 0);\n const ourFingerprint = this.#storage.fingerprint(curr, curr + bucketSize);\n curr += bucketSize;\n let nextBound: Bound;\n if (curr === upper) {\n nextBound = upperBound;\n } else {\n const at = curr - 1;\n let prevItem: NegItem | undefined;\n let currItem: NegItem | undefined;\n this.#storage.iterate(at, at + 2, (item, index) => {\n if (index === at) {\n prevItem = item;\n } else {\n currItem = item;\n }\n return true;\n });\n if (prevItem === undefined || currItem === undefined) {\n throw new Nip77Error(\"missing bound items\");\n }\n nextBound = this.#minimalBound(prevItem, currItem);\n }\n o.extend(this.#encodeBound(nextBound));\n o.extend(encodeVarInt(Mode.Fingerprint));\n o.extend(ourFingerprint);\n }\n }\n\n #exceededFrameSizeLimit(n: number): boolean {\n if (this.#frameSizeLimit === 0) {\n return false;\n }\n return n > this.#frameSizeLimit - 200;\n }\n\n #decodeTimestampIn(encoded: EncodedBuf): number {\n let timestamp = decodeVarInt(encoded);\n timestamp = timestamp === 0 ? INFINITY : timestamp - 1;\n if (this.#lastTimestampIn === INFINITY || timestamp === INFINITY) {\n this.#lastTimestampIn = INFINITY;\n return INFINITY;\n }\n timestamp += this.#lastTimestampIn;\n this.#lastTimestampIn = timestamp;\n return timestamp;\n }\n\n #decodeBound(encoded: EncodedBuf): Bound {\n const timestamp = this.#decodeTimestampIn(encoded);\n const len = decodeVarInt(encoded);\n if (len > ID_SIZE) {\n throw new Nip77Error(\"bound key too long\");\n }\n return { timestamp, id: getBytes(encoded, len) };\n }\n\n #encodeTimestampOut(timestamp: number): EncodedBuf {\n if (timestamp === INFINITY) {\n this.#lastTimestampOut = INFINITY;\n return encodeVarInt(0);\n }\n const delta = timestamp - this.#lastTimestampOut;\n this.#lastTimestampOut = timestamp;\n return encodeVarInt(delta + 1);\n }\n\n #encodeSkipInto(o: EncodedBuf, bound: Bound): void {\n o.extend(this.#encodeBound(bound));\n o.extend(encodeVarInt(Mode.Skip));\n }\n\n #encodeBound(key: Bound): EncodedBuf {\n const output = new EncodedBuf();\n output.extend(this.#encodeTimestampOut(key.timestamp));\n output.extend(encodeVarInt(key.id.length));\n output.extend(key.id);\n return output;\n }\n\n #minimalBound(prev: NegItem, curr: NegItem): Bound {\n if (curr.timestamp !== prev.timestamp) {\n return this.#bound(curr.timestamp);\n }\n let shared = 0;\n for (let i = 0; i < ID_SIZE; i++) {\n if (curr.id[i] !== prev.id[i]) {\n break;\n }\n shared += 1;\n }\n return this.#bound(curr.timestamp, curr.id.subarray(0, shared + 1));\n }\n}\n\n/** Drive initiate/reconcile until nextMessage is null. Transport-free. */\nexport async function runNegSession(opts: {\n storage: NegentropyStorageVector;\n frameSizeLimit?: number;\n openingSend: (hex: string) => void;\n msgSend: (hex: string) => void;\n next: () => Promise<string>;\n}): Promise<{ have: string[]; need: string[] }> {\n const neg = new Negentropy(opts.storage, opts.frameSizeLimit);\n const have = new Set<string>();\n const need = new Set<string>();\n opts.openingSend(neg.initiate());\n for (let round = 0; round < MAX_NEG_ROUNDS; round++) {\n // oxlint-disable-next-line no-await-in-loop -- protocol rounds are sequential by definition\n const out = neg.reconcile(await opts.next());\n for (const id of out.have) {\n have.add(id);\n }\n for (const id of out.need) {\n need.add(id);\n }\n if (out.nextMessage === undefined) {\n return { have: [...have], need: [...need] };\n }\n opts.msgSend(out.nextMessage);\n }\n throw new Nip77Error(\"negentropy exceeded max rounds\");\n}\n"],"mappings":"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"}
|
package/dist/nips/nip98.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { d as nowSeconds, f as utf8Decoder, i as bytesToHex, p as utf8Encoder, v as NostrError } from "../util-
|
|
1
|
+
import { d as nowSeconds, f as utf8Decoder, i as bytesToHex, p as utf8Encoder, v as NostrError } from "../util-DMF9AIO0.mjs";
|
|
2
2
|
import { t as Kind } from "../kind-Cxwhhkx8.mjs";
|
|
3
|
-
import { _ as firstTagValue, m as validateSignedEvent } from "../event-
|
|
4
|
-
import { n as verifyEvent } from "../verifier-
|
|
3
|
+
import { _ as firstTagValue, m as validateSignedEvent } from "../event-CHf47dPM.mjs";
|
|
4
|
+
import { n as verifyEvent } from "../verifier-B51Wkt5H.mjs";
|
|
5
5
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
6
6
|
import { base64 } from "@scure/base";
|
|
7
7
|
//#region src/nips/nip98.ts
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { n as EventTemplate, t as Event } from "./event-DJeb_DIL.mjs";
|
|
2
2
|
import { t as Filter } from "./filter-jI_9htF7.mjs";
|
|
3
|
-
import { n as CountResult } from "./message-
|
|
3
|
+
import { n as CountResult } from "./message-tclYCVNc.mjs";
|
|
4
4
|
import { t as EventVerifier } from "./verifier-CgTFpgsC.mjs";
|
|
5
|
-
import { a as NegentropyStorageVector } from "./nip77-
|
|
5
|
+
import { a as NegentropyStorageVector } from "./nip77-DzUwUxHq.mjs";
|
|
6
6
|
import { t as WebSocketConstructor } from "./websocket-CmeqvAkp.mjs";
|
|
7
7
|
//#region src/relay/subscription.d.ts
|
|
8
8
|
type SubscriptionHandlers = {
|
|
@@ -21,8 +21,9 @@ type SubscriptionHandlers = {
|
|
|
21
21
|
type SubscribeOptions = SubscriptionHandlers & {
|
|
22
22
|
id?: string | undefined;
|
|
23
23
|
/**
|
|
24
|
-
* If set, fire `oneose` once after this many ms if EOSE has not arrived.
|
|
25
|
-
* `
|
|
24
|
+
* If set, fire `oneose` once after this many ms if EOSE has not arrived. The synthesized EOSE
|
|
25
|
+
* also closes a `closeOnEose` subscription — without it a one-shot REQ would hang until a real
|
|
26
|
+
* EOSE that is then ignored.
|
|
26
27
|
*/
|
|
27
28
|
eoseTimeoutMs?: number | undefined;
|
|
28
29
|
/**
|
|
@@ -48,7 +49,7 @@ declare function subscriptionToAsyncIterable(start: (handlers: SubscriptionHandl
|
|
|
48
49
|
includeEose?: boolean | undefined;
|
|
49
50
|
}): AsyncIterable<Event> & Closer;
|
|
50
51
|
//#endregion
|
|
51
|
-
//#region src/relay/
|
|
52
|
+
//#region src/relay/fetch.d.ts
|
|
52
53
|
/** How a one-shot {@link Relay.fetch} REQ ended. */
|
|
53
54
|
type RelayFetchEnd = {
|
|
54
55
|
readonly type: "eose";
|
|
@@ -60,10 +61,17 @@ type RelayFetchEnd = {
|
|
|
60
61
|
};
|
|
61
62
|
/** `Relay.fetch` result: collected events plus the REQ's end reason. */
|
|
62
63
|
type RelayFetchResult = {
|
|
63
|
-
readonly events: Event
|
|
64
|
+
readonly events: ReadonlyArray<Event>;
|
|
64
65
|
readonly end: RelayFetchEnd;
|
|
65
66
|
};
|
|
66
67
|
//#endregion
|
|
68
|
+
//#region src/relay/ok-tracker.d.ts
|
|
69
|
+
/** A relay's NIP-01 OK reply to a published event. */
|
|
70
|
+
type PublishResult = {
|
|
71
|
+
readonly ok: boolean;
|
|
72
|
+
readonly message: string;
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
67
75
|
//#region src/relay/relay.d.ts
|
|
68
76
|
/**
|
|
69
77
|
* Relay lifecycle states:
|
|
@@ -104,11 +112,6 @@ type RelayOptions = {
|
|
|
104
112
|
/** When set, CLOSED/OK `auth-required:` triggers AUTH then retries the REQ/EVENT/COUNT. */
|
|
105
113
|
authSigner?: ((template: EventTemplate) => Promise<Event>) | undefined;
|
|
106
114
|
};
|
|
107
|
-
/** A relay's NIP-01 OK reply to a published event. */
|
|
108
|
-
type PublishResult = {
|
|
109
|
-
ok: boolean;
|
|
110
|
-
message: string;
|
|
111
|
-
};
|
|
112
115
|
/** Typed event payloads for {@link Relay.on}: lifecycle and protocol notifications. */
|
|
113
116
|
type RelayEventMap = {
|
|
114
117
|
/** A NIP-01 NOTICE message from the relay. */
|
|
@@ -248,7 +251,6 @@ type PoolOptions = Omit<RelayOptions, "authSigner"> & {
|
|
|
248
251
|
/** Close unused relays. Unset/0 = disabled. */
|
|
249
252
|
idleTimeoutMs?: number | undefined;
|
|
250
253
|
idleCleanupIntervalMs?: number | undefined;
|
|
251
|
-
onIdleRelaysClosed?: ((urls: string[]) => void) | undefined;
|
|
252
254
|
/**
|
|
253
255
|
* Soft cap on connected non-pinned relays. When `ensureRelay` would create a new non-pinned relay
|
|
254
256
|
* at the cap, the least-recently-used idle one is closed first (idle = no subscriptions and no
|
|
@@ -265,11 +267,19 @@ type PoolOptions = Omit<RelayOptions, "authSigner"> & {
|
|
|
265
267
|
* through the normal reconnect path. Unset = events are dropped without counting, as before.
|
|
266
268
|
*/
|
|
267
269
|
invalidEventPolicy?: InvalidEventPolicy | undefined;
|
|
270
|
+
};
|
|
271
|
+
/** Typed event payloads for {@link Pool.on}. */
|
|
272
|
+
type PoolEventMap = {
|
|
273
|
+
/** Normalized URLs closed by an idle-cleanup pass. */
|
|
274
|
+
idle: ReadonlyArray<string>;
|
|
268
275
|
/**
|
|
269
|
-
*
|
|
270
|
-
*
|
|
276
|
+
* A relay suspended for exceeding `invalidEventPolicy`: `url` is the normalized relay URL and
|
|
277
|
+
* `until` the epoch-ms time the suspension lifts.
|
|
271
278
|
*/
|
|
272
|
-
|
|
279
|
+
suspend: {
|
|
280
|
+
readonly url: string;
|
|
281
|
+
readonly until: number;
|
|
282
|
+
};
|
|
273
283
|
};
|
|
274
284
|
/** Per-relay publish outcome: the relay's OK verdict or a transport-level failure. */
|
|
275
285
|
type PoolPublishResult = {
|
|
@@ -283,7 +293,7 @@ type PoolPublishResult = {
|
|
|
283
293
|
} | {
|
|
284
294
|
readonly url: string;
|
|
285
295
|
readonly status: "failed";
|
|
286
|
-
readonly error:
|
|
296
|
+
readonly error: Error;
|
|
287
297
|
};
|
|
288
298
|
/** Multi-relay subscribe options: callbacks also receive the normalized relay URL. */
|
|
289
299
|
type PoolSubscribeOptions = Omit<SubscribeOptions, "onevent" | "receivedEvent"> & {
|
|
@@ -292,11 +302,11 @@ type PoolSubscribeOptions = Omit<SubscribeOptions, "onevent" | "receivedEvent">
|
|
|
292
302
|
};
|
|
293
303
|
/** Per-relay one-shot fetch outcome: collected events plus how its REQ ended. */
|
|
294
304
|
type PoolFetchResult = {
|
|
295
|
-
url: string;
|
|
296
|
-
events: Event
|
|
297
|
-
end: RelayFetchEnd | {
|
|
305
|
+
readonly url: string;
|
|
306
|
+
readonly events: ReadonlyArray<Event>;
|
|
307
|
+
readonly end: RelayFetchEnd | {
|
|
298
308
|
readonly type: "failed";
|
|
299
|
-
readonly
|
|
309
|
+
readonly error: Error;
|
|
300
310
|
};
|
|
301
311
|
};
|
|
302
312
|
/** Per-relay NIP-45 COUNT result (or a transport-level failure). */
|
|
@@ -306,12 +316,18 @@ type PoolCountResult = ({
|
|
|
306
316
|
} & CountResult) | {
|
|
307
317
|
readonly url: string;
|
|
308
318
|
readonly status: "failed";
|
|
309
|
-
readonly error:
|
|
319
|
+
readonly error: Error;
|
|
310
320
|
};
|
|
311
321
|
/** Multi-relay coordinator: connection reuse, cross-relay event dedup, fan-out publish. */
|
|
312
322
|
declare class Pool {
|
|
313
323
|
#private;
|
|
314
324
|
constructor(opts?: PoolOptions);
|
|
325
|
+
/**
|
|
326
|
+
* Listen for a pool event. Multiple listeners per type are allowed and fire in registration
|
|
327
|
+
* order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe
|
|
328
|
+
* function.
|
|
329
|
+
*/
|
|
330
|
+
on<K extends keyof PoolEventMap>(type: K, listener: (payload: PoolEventMap[K]) => void): () => void;
|
|
315
331
|
setAllowInsecure(allow: boolean): void;
|
|
316
332
|
setTrustedInsecureUrls(urls: ReadonlyArray<string>): void;
|
|
317
333
|
setPinnedUrls(urls: ReadonlyArray<string>): void;
|
|
@@ -325,7 +341,10 @@ declare class Pool {
|
|
|
325
341
|
signal?: AbortSignal | undefined;
|
|
326
342
|
timeoutMs?: number | undefined;
|
|
327
343
|
}): Promise<Relay>;
|
|
328
|
-
|
|
344
|
+
/** Close every pooled relay and clear all pool state. */
|
|
345
|
+
close(): void;
|
|
346
|
+
/** Close and forget the listed relays; other pooled relays are untouched. */
|
|
347
|
+
closeRelays(urls: ReadonlyArray<string>): void;
|
|
329
348
|
/**
|
|
330
349
|
* Subscribe across relays. Deduplicates by event id. Returns a closer; callbacks receive every
|
|
331
350
|
* new event once.
|
|
@@ -372,5 +391,5 @@ declare class Pool {
|
|
|
372
391
|
connectedUrls(): string[];
|
|
373
392
|
}
|
|
374
393
|
//#endregion
|
|
375
|
-
export {
|
|
376
|
-
//# sourceMappingURL=pool-
|
|
394
|
+
export { Closer as _, PoolFetchResult as a, SubscriptionHandlers as b, PoolSubscribeOptions as c, RelayOptions as d, RelayStatus as f, RelayFetchResult as g, RelayFetchEnd as h, PoolEventMap as i, Relay as l, PublishResult as m, Pool as n, PoolOptions as o, RelayStatusName as p, PoolCountResult as r, PoolPublishResult as s, InvalidEventPolicy as t, RelayEventMap as u, RelaySubscription as v, subscriptionToAsyncIterable as x, SubscribeOptions as y };
|
|
395
|
+
//# sourceMappingURL=pool-CqQvUnsq.d.mts.map
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as toError, _ as MessageError, u as normalizeURL } from "./util-DMF9AIO0.mjs";
|
|
2
2
|
import { n as canonicalizeFilters } from "./filter-BQl3GlHt.mjs";
|
|
3
|
-
import { t as assertSubscriptionId } from "./message-
|
|
3
|
+
import { t as assertSubscriptionId } from "./message-oNr7t0SJ.mjs";
|
|
4
4
|
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
5
|
-
import { c as RelayConnectionError, d as RelaySuspendedError, n as Relay, r as RelayStatus, s as RelayClosedError, u as RelayPublishError } from "./relay-
|
|
5
|
+
import { c as RelayConnectionError, d as RelaySuspendedError, n as Relay, r as RelayStatus, s as RelayClosedError, u as RelayPublishError } from "./relay-quCs7MHD.mjs";
|
|
6
6
|
import { i as throwIfAborted, n as onAbort, t as abortReason } from "./abort-DBq2STPj.mjs";
|
|
7
|
+
import { t as Emitter } from "./emitter-DkCY6NkU.mjs";
|
|
7
8
|
//#region src/relay/url.ts
|
|
8
9
|
/**
|
|
9
10
|
* {@link normalizeURL}, or the raw string when it is not a valid relay URL (ensureRelay then fails
|
|
@@ -228,10 +229,9 @@ var Pool = class {
|
|
|
228
229
|
#relays = /* @__PURE__ */ new Map();
|
|
229
230
|
#relayOptions;
|
|
230
231
|
#automaticallyAuth;
|
|
231
|
-
#onIdleRelaysClosed;
|
|
232
232
|
#maxRelays;
|
|
233
233
|
#invalidEventPolicy;
|
|
234
|
-
#
|
|
234
|
+
#events = new Emitter();
|
|
235
235
|
#lastActivity = /* @__PURE__ */ new Map();
|
|
236
236
|
#idleTimeoutMs;
|
|
237
237
|
#idleTimer;
|
|
@@ -244,19 +244,25 @@ var Pool = class {
|
|
|
244
244
|
#suspendedUntil = /* @__PURE__ */ new Map();
|
|
245
245
|
#resumeTimers = /* @__PURE__ */ new Map();
|
|
246
246
|
constructor(opts = {}) {
|
|
247
|
-
const { automaticallyAuth, allowInsecure, trustedInsecureUrls, idleTimeoutMs, idleCleanupIntervalMs,
|
|
247
|
+
const { automaticallyAuth, allowInsecure, trustedInsecureUrls, idleTimeoutMs, idleCleanupIntervalMs, maxRelays, pinnedUrls, invalidEventPolicy, ...relayOptions } = opts;
|
|
248
248
|
this.#relayOptions = relayOptions;
|
|
249
249
|
this.#automaticallyAuth = automaticallyAuth;
|
|
250
|
-
this.#onIdleRelaysClosed = onIdleRelaysClosed;
|
|
251
250
|
this.#maxRelays = maxRelays;
|
|
252
251
|
this.#invalidEventPolicy = invalidEventPolicy;
|
|
253
|
-
this.#onRelaySuspended = onRelaySuspended;
|
|
254
252
|
this.#allowInsecure = allowInsecure ?? false;
|
|
255
253
|
this.#trustedInsecure = new Set((trustedInsecureUrls ?? []).map(normalizeURL));
|
|
256
254
|
this.#pinned = new Set((pinnedUrls ?? []).map(normalizeURL));
|
|
257
255
|
this.#idleTimeoutMs = idleTimeoutMs ?? 0;
|
|
258
256
|
if (this.#idleTimeoutMs > 0) this.#idleTimer = setInterval(() => this.cleanIdleRelays(), idleCleanupIntervalMs ?? 3e4);
|
|
259
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Listen for a pool event. Multiple listeners per type are allowed and fire in registration
|
|
260
|
+
* order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe
|
|
261
|
+
* function.
|
|
262
|
+
*/
|
|
263
|
+
on(type, listener) {
|
|
264
|
+
return this.#events.on(type, listener);
|
|
265
|
+
}
|
|
260
266
|
setAllowInsecure(allow) {
|
|
261
267
|
this.#allowInsecure = allow;
|
|
262
268
|
}
|
|
@@ -284,15 +290,15 @@ var Pool = class {
|
|
|
284
290
|
}
|
|
285
291
|
for (const url of this.#lastActivity.keys()) if (!this.#relays.has(url)) this.#lastActivity.delete(url);
|
|
286
292
|
if (idle.length === 0) return;
|
|
287
|
-
this.
|
|
288
|
-
|
|
293
|
+
this.closeRelays(idle);
|
|
294
|
+
this.#events.emit("idle", idle);
|
|
289
295
|
}
|
|
290
296
|
#touch(url) {
|
|
291
297
|
this.#lastActivity.set(url, Date.now());
|
|
292
298
|
}
|
|
293
299
|
/**
|
|
294
300
|
* Count one verification failure for `norm`; on the `limit + 1`-th inside `windowMs` suspend the
|
|
295
|
-
* relay: drop its connection (subscriptions kept), record `until`, and
|
|
301
|
+
* relay: drop its connection (subscriptions kept), record `until`, and emit `suspend`.
|
|
296
302
|
*/
|
|
297
303
|
#noteInvalidEvent(norm) {
|
|
298
304
|
const policy = this.#invalidEventPolicy;
|
|
@@ -321,7 +327,10 @@ var Pool = class {
|
|
|
321
327
|
})();
|
|
322
328
|
}, policy.cooldownMs);
|
|
323
329
|
this.#resumeTimers.set(norm, resume);
|
|
324
|
-
|
|
330
|
+
this.#events.emit("suspend", {
|
|
331
|
+
url: norm,
|
|
332
|
+
until
|
|
333
|
+
});
|
|
325
334
|
}
|
|
326
335
|
#rejectInsecure(url, norm) {
|
|
327
336
|
if (this.#allowInsecure) return;
|
|
@@ -355,7 +364,7 @@ var Pool = class {
|
|
|
355
364
|
oldestUrl = url;
|
|
356
365
|
}
|
|
357
366
|
}
|
|
358
|
-
if (count >= cap && oldestUrl !== void 0) this.
|
|
367
|
+
if (count >= cap && oldestUrl !== void 0) this.closeRelays([oldestUrl]);
|
|
359
368
|
}
|
|
360
369
|
async ensureRelay(url, opts) {
|
|
361
370
|
const norm = normalizeURL(url);
|
|
@@ -405,18 +414,19 @@ var Pool = class {
|
|
|
405
414
|
}
|
|
406
415
|
return relay;
|
|
407
416
|
}
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
417
|
+
/** Close every pooled relay and clear all pool state. */
|
|
418
|
+
close() {
|
|
419
|
+
this.#stopIdleCleanup();
|
|
420
|
+
for (const timer of this.#resumeTimers.values()) clearTimeout(timer);
|
|
421
|
+
this.#resumeTimers.clear();
|
|
422
|
+
this.#suspendedUntil.clear();
|
|
423
|
+
this.#invalidEvents.clear();
|
|
424
|
+
for (const relay of this.#relays.values()) relay.close();
|
|
425
|
+
this.#relays.clear();
|
|
426
|
+
this.#lastActivity.clear();
|
|
427
|
+
}
|
|
428
|
+
/** Close and forget the listed relays; other pooled relays are untouched. */
|
|
429
|
+
closeRelays(urls) {
|
|
420
430
|
for (const url of urls) {
|
|
421
431
|
const norm = normalizeURL(url);
|
|
422
432
|
const timer = this.#resumeTimers.get(norm);
|
|
@@ -485,7 +495,7 @@ var Pool = class {
|
|
|
485
495
|
events: [],
|
|
486
496
|
end: {
|
|
487
497
|
type: "failed",
|
|
488
|
-
|
|
498
|
+
error: toError(error)
|
|
489
499
|
}
|
|
490
500
|
};
|
|
491
501
|
}
|
|
@@ -524,7 +534,7 @@ var Pool = class {
|
|
|
524
534
|
return {
|
|
525
535
|
url,
|
|
526
536
|
status: "failed",
|
|
527
|
-
error:
|
|
537
|
+
error: toError(error)
|
|
528
538
|
};
|
|
529
539
|
}
|
|
530
540
|
}));
|
|
@@ -567,7 +577,7 @@ var Pool = class {
|
|
|
567
577
|
return {
|
|
568
578
|
url,
|
|
569
579
|
status: "failed",
|
|
570
|
-
error:
|
|
580
|
+
error: toError(error)
|
|
571
581
|
};
|
|
572
582
|
}
|
|
573
583
|
}));
|
|
@@ -593,4 +603,4 @@ var Pool = class {
|
|
|
593
603
|
//#endregion
|
|
594
604
|
export { isInsecureRelayUrl as i, fanIn as n, fetchRouted as r, Pool as t };
|
|
595
605
|
|
|
596
|
-
//# sourceMappingURL=pool-
|
|
606
|
+
//# sourceMappingURL=pool-vlw74C4t.mjs.map
|