@qntx/nostr 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{builder-aNU2gGFP.mjs → builder-CVRmUeNm.mjs} +4 -4
- package/dist/{builder-aNU2gGFP.mjs.map → builder-CVRmUeNm.mjs.map} +1 -1
- package/dist/{builder-CTwkB2zM.d.mts → builder-GBddxj8i.d.mts} +3 -3
- package/dist/{client-Cj2lUaVh.mjs → client-CeyDLoyO.mjs} +50 -28
- package/dist/client-CeyDLoyO.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/core.d.mts +8 -8
- package/dist/core.mjs +8 -8
- package/dist/{event-CHf47dPM.mjs → event-C2eLiPpV.mjs} +15 -5
- package/dist/event-C2eLiPpV.mjs.map +1 -0
- package/dist/{event-DJeb_DIL.d.mts → event-j3BwI5dF.d.mts} +2 -4
- package/dist/{filter-jI_9htF7.d.mts → filter-CfEgQBXt.d.mts} +3 -5
- package/dist/{filter-BQl3GlHt.mjs → filter-TrF2LEoy.mjs} +3 -36
- package/dist/filter-TrF2LEoy.mjs.map +1 -0
- package/dist/{gossip-DFxIBlsr.mjs → gossip-5ezg0Quo.mjs} +3 -3
- package/dist/gossip-5ezg0Quo.mjs.map +1 -0
- package/dist/{gossip-OOeeFzG5.d.mts → gossip-BWb9HmfK.d.mts} +8 -5
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/http-CuazNRIP.mjs.map +1 -1
- package/dist/{http-C4HwIu4B.d.mts → http-kwWG-V1X.d.mts} +9 -3
- package/dist/{index-C_w2WfVz.d.mts → index-C0mFpelb.d.mts} +6 -6
- package/dist/{index-Cl_1XdYP.d.mts → index-C1m5cHpd.d.mts} +22 -14
- package/dist/{index-BxCBX-8R.d.mts → index-CoX7SnO7.d.mts} +7 -4
- package/dist/{index-CPVEyzNX.d.mts → index-D0M3kjY2.d.mts} +9 -9
- package/dist/{index-DU9tRFTZ.d.mts → index-Hp26bgYG.d.mts} +11 -4
- package/dist/{index-DyKwQnbt.d.mts → index-JD1O7m2v.d.mts} +4 -4
- package/dist/index.d.mts +19 -19
- package/dist/index.mjs +17 -17
- package/dist/{instance-C62w3cEt.mjs → instance-CI_IaZpf.mjs} +2 -2
- package/dist/{instance-C62w3cEt.mjs.map → instance-CI_IaZpf.mjs.map} +1 -1
- package/dist/{instance-DjsL8h84.d.mts → instance-X_WkCnpR.d.mts} +3 -3
- package/dist/{key-D_5OgGT7.d.mts → key-CyKN1RTi.d.mts} +2 -2
- package/dist/{key-DnVpchl6.mjs → key-DY_HEIpY.mjs} +5 -4
- package/dist/key-DY_HEIpY.mjs.map +1 -0
- package/dist/{kind-Cxwhhkx8.mjs → kind-D8fENiiL.mjs} +11 -5
- package/dist/kind-D8fENiiL.mjs.map +1 -0
- package/dist/{loaders-BmPkQfzI.mjs → loaders-DifimjJ7.mjs} +9 -8
- package/dist/{loaders-BmPkQfzI.mjs.map → loaders-DifimjJ7.mjs.map} +1 -1
- package/dist/loaders.d.mts +2 -2
- package/dist/loaders.mjs +1 -1
- package/dist/{memory-CdJ07kub.mjs → memory-DENGQ_f6.mjs} +2 -2
- package/dist/{memory-CdJ07kub.mjs.map → memory-DENGQ_f6.mjs.map} +1 -1
- package/dist/{memory-index-Cbc83vOZ.mjs → memory-index-D_lvlYAy.mjs} +4 -4
- package/dist/{memory-index-Cbc83vOZ.mjs.map → memory-index-D_lvlYAy.mjs.map} +1 -1
- package/dist/{message-tclYCVNc.d.mts → message-CcUz22qh.d.mts} +3 -3
- package/dist/{message-oNr7t0SJ.mjs → message-Rd5Z_17h.mjs} +5 -4
- package/dist/{message-oNr7t0SJ.mjs.map → message-Rd5Z_17h.mjs.map} +1 -1
- package/dist/{nip17-B5WgW4C4.d.mts → nip17-B7MI82Iq.d.mts} +4 -4
- package/dist/{nip51-CR0Q8eym.d.mts → nip51-O_vhOiK2.d.mts} +3 -3
- package/dist/{nip59-C-RWFQIv.d.mts → nip59-CdDoJptA.d.mts} +2 -2
- package/dist/{nip65-D1fbmhOg.d.mts → nip65-BL79HI8N.d.mts} +3 -3
- package/dist/{nip77-DzUwUxHq.d.mts → nip77-wHA_nEV5.d.mts} +2 -2
- package/dist/nips/blossom.d.mts +3 -3
- package/dist/nips/blossom.mjs +2 -2
- package/dist/nips/nip05.d.mts +1 -1
- package/dist/nips/nip10.d.mts +2 -2
- package/dist/nips/nip10.mjs +3 -3
- package/dist/nips/nip11.d.mts +1 -1
- package/dist/nips/nip13.d.mts +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +3 -3
- package/dist/nips/nip27.d.mts +1 -1
- package/dist/nips/nip42.d.mts +1 -1
- package/dist/nips/nip42.mjs +1 -1
- package/dist/nips/nip51.d.mts +1 -1
- package/dist/nips/nip51.mjs +3 -3
- package/dist/nips/nip57.d.mts +1 -1
- package/dist/nips/nip57.mjs +3 -3
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +4 -4
- 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 +8 -5
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +1 -1
- package/dist/nips/nip98.mjs +3 -3
- package/dist/{pool-CqQvUnsq.d.mts → pool-BDI_u03Q.d.mts} +8 -7
- package/dist/{pool-vlw74C4t.mjs → pool-jok2-iBJ.mjs} +28 -21
- package/dist/pool-jok2-iBJ.mjs.map +1 -0
- package/dist/{put-w13ci3sC.mjs → put-JsiCKMyZ.mjs} +4 -4
- package/dist/put-JsiCKMyZ.mjs.map +1 -0
- package/dist/{reactive-Tz0otDIP.mjs → reactive-6Wn9qf_d.mjs} +5 -5
- package/dist/{reactive-Tz0otDIP.mjs.map → reactive-6Wn9qf_d.mjs.map} +1 -1
- package/dist/{reactive-gGqUrzW8.d.mts → reactive-X4p6nABy.d.mts} +4 -4
- package/dist/{relay-quCs7MHD.mjs → relay-__QtO33h.mjs} +18 -10
- package/dist/relay-__QtO33h.mjs.map +1 -0
- package/dist/relay.d.mts +3 -3
- package/dist/relay.mjs +2 -2
- package/dist/{signer-poTGe6Er.mjs → signer-D7PtVvQn.mjs} +14 -7
- package/dist/signer-D7PtVvQn.mjs.map +1 -0
- package/dist/signer.d.mts +2 -2
- package/dist/signer.mjs +1 -1
- package/dist/{storage-B5IXTVuS.mjs → storage-oihjE0_x.mjs} +10 -10
- package/dist/{storage-B5IXTVuS.mjs.map → storage-oihjE0_x.mjs.map} +1 -1
- package/dist/storage.d.mts +2 -2
- package/dist/storage.mjs +3 -3
- package/dist/store.d.mts +1 -1
- package/dist/store.mjs +1 -1
- package/dist/testing.d.mts +2 -2
- package/dist/testing.mjs +7 -7
- package/dist/{types-DNmiscfd.d.mts → types-BJ4wZ7JJ.d.mts} +2 -2
- package/dist/{types-BacwnDtU.d.mts → types-Ccox7-5v.d.mts} +3 -3
- package/dist/{verifier-CgTFpgsC.d.mts → verifier-BjL2M0ka.d.mts} +2 -2
- package/dist/{verifier-B51Wkt5H.mjs → verifier-uC_jd-Nw.mjs} +3 -3
- package/dist/verifier-uC_jd-Nw.mjs.map +1 -0
- package/dist/wasm.browser.d.mts +1 -1
- package/dist/wasm.browser.mjs +1 -1
- package/dist/wasm.d.mts +1 -1
- package/dist/wasm.mjs +1 -1
- package/package.json +5 -5
- package/dist/client-Cj2lUaVh.mjs.map +0 -1
- package/dist/event-CHf47dPM.mjs.map +0 -1
- package/dist/filter-BQl3GlHt.mjs.map +0 -1
- package/dist/gossip-DFxIBlsr.mjs.map +0 -1
- package/dist/key-DnVpchl6.mjs.map +0 -1
- package/dist/kind-Cxwhhkx8.mjs.map +0 -1
- package/dist/pool-vlw74C4t.mjs.map +0 -1
- package/dist/put-w13ci3sC.mjs.map +0 -1
- package/dist/relay-quCs7MHD.mjs.map +0 -1
- package/dist/signer-poTGe6Er.mjs.map +0 -1
- package/dist/verifier-B51Wkt5H.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { t as Event } from "./event-
|
|
2
|
-
import { t as Filter } from "./filter-
|
|
1
|
+
import { t as Event } from "./event-j3BwI5dF.mjs";
|
|
2
|
+
import { t as Filter } from "./filter-CfEgQBXt.mjs";
|
|
3
3
|
//#region src/storage/types.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* `"invalid"`: the event failed canonical shape validation (see `validateSignedEvent`); it was not
|
|
@@ -44,4 +44,4 @@ type EventStore = {
|
|
|
44
44
|
};
|
|
45
45
|
//#endregion
|
|
46
46
|
export { PutResult as i, NegentropyItem as n, OutboxBound as r, EventStore as t };
|
|
47
|
-
//# sourceMappingURL=types-
|
|
47
|
+
//# sourceMappingURL=types-Ccox7-5v.d.mts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as Event } from "./event-
|
|
1
|
+
import { t as Event } from "./event-j3BwI5dF.mjs";
|
|
2
2
|
//#region src/core/verifier.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* Verification backend: return true iff `sha256(serializedUtf8)` equals `id` and `sig` is a valid
|
|
@@ -19,4 +19,4 @@ declare function createEventVerifier(backend: SerializedEventVerifier): EventVer
|
|
|
19
19
|
declare const verifyEvent: EventVerifier;
|
|
20
20
|
//#endregion
|
|
21
21
|
export { verifyEvent as i, SerializedEventVerifier as n, createEventVerifier as r, EventVerifier as t };
|
|
22
|
-
//# sourceMappingURL=verifier-
|
|
22
|
+
//# sourceMappingURL=verifier-BjL2M0ka.d.mts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as hexToBytes, p as utf8Encoder } from "./util-DMF9AIO0.mjs";
|
|
2
|
-
import { c as markVerified,
|
|
2
|
+
import { c as markVerified, h as validateSignedEvent, i as isMarkedVerified, r as isMarkedFailed, s as markUnverified, u as serializeValidatedEvent } from "./event-C2eLiPpV.mjs";
|
|
3
3
|
import { sha256 } from "@noble/hashes/sha2.js";
|
|
4
4
|
import { schnorr } from "@noble/curves/secp256k1.js";
|
|
5
5
|
//#region src/core/verifier.ts
|
|
@@ -16,7 +16,7 @@ function createEventVerifier(backend) {
|
|
|
16
16
|
markUnverified(event);
|
|
17
17
|
return false;
|
|
18
18
|
}
|
|
19
|
-
const ok = backend(utf8Encoder.encode(
|
|
19
|
+
const ok = backend(utf8Encoder.encode(serializeValidatedEvent(event)), hexToBytes(event.id), hexToBytes(event.pubkey), hexToBytes(event.sig));
|
|
20
20
|
if (ok) markVerified(event);
|
|
21
21
|
else markUnverified(event);
|
|
22
22
|
return ok;
|
|
@@ -35,4 +35,4 @@ const verifyEvent = createEventVerifier(nobleSerializedVerify);
|
|
|
35
35
|
//#endregion
|
|
36
36
|
export { verifyEvent as n, createEventVerifier as t };
|
|
37
37
|
|
|
38
|
-
//# sourceMappingURL=verifier-
|
|
38
|
+
//# sourceMappingURL=verifier-uC_jd-Nw.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verifier-uC_jd-Nw.mjs","names":[],"sources":["../src/core/verifier.ts"],"sourcesContent":["import { schnorr } from \"@noble/curves/secp256k1.js\";\nimport { sha256 } from \"@noble/hashes/sha2.js\";\n\nimport {\n isMarkedFailed,\n isMarkedVerified,\n markUnverified,\n markVerified,\n serializeValidatedEvent,\n validateSignedEvent,\n} from \"./event.ts\";\nimport type { Event } from \"./event.ts\";\nimport { hexToBytes, utf8Encoder } from \"./util.ts\";\n\n/**\n * Verification backend: return true iff `sha256(serializedUtf8)` equals `id` and `sig` is a valid\n * BIP-340 signature of `id` by the x-only `pubkey`. Inputs are raw bytes (`id`/`pubkey` 32, `sig`\n * 64). Exceptions propagate through the built verifier unmarked — a throwing backend is broken and\n * must surface.\n */\nexport type SerializedEventVerifier = (\n serializedUtf8: Uint8Array,\n id: Uint8Array,\n pubkey: Uint8Array,\n sig: Uint8Array,\n) => boolean;\n\n/** Whole-event verifier: return true iff the event's id and BIP-340 signature verify. */\nexport type EventVerifier = (event: Event) => boolean;\n\n/**\n * Build an event verifier with exactly the semantics of {@link verifyEvent}, delegating the hash and\n * signature check to `backend`. Verified/failed results share `verifyEvent`'s WeakSet caches, so\n * verifiers built this way and `verifyEvent` agree on (and reuse) each other's results.\n */\nexport function createEventVerifier(backend: SerializedEventVerifier): EventVerifier {\n return (event) => {\n if (isMarkedVerified(event)) {\n return true;\n }\n if (isMarkedFailed(event)) {\n return false;\n }\n if (!validateSignedEvent(event)) {\n markUnverified(event);\n return false;\n }\n const ok = backend(\n utf8Encoder.encode(serializeValidatedEvent(event)),\n hexToBytes(event.id),\n hexToBytes(event.pubkey),\n hexToBytes(event.sig),\n );\n if (ok) {\n markVerified(event);\n } else {\n markUnverified(event);\n }\n return ok;\n };\n}\n\nconst nobleSerializedVerify: SerializedEventVerifier = (serializedUtf8, id, pubkey, sig) => {\n try {\n const digest = sha256(serializedUtf8);\n if (!digest.every((byte, i) => byte === id[i])) {\n return false;\n }\n return schnorr.verify(sig, id, pubkey);\n } catch {\n return false;\n }\n};\n\n/** Verify event id and BIP-340 signature. Uses WeakSet cache (does not mutate the event). */\nexport const verifyEvent: EventVerifier = createEventVerifier(nobleSerializedVerify);\n"],"mappings":";;;;;;;;;;AAmCA,SAAgB,oBAAoB,SAAiD;CACnF,QAAQ,UAAU;EAChB,IAAI,iBAAiB,KAAK,GACxB,OAAO;EAET,IAAI,eAAe,KAAK,GACtB,OAAO;EAET,IAAI,CAAC,oBAAoB,KAAK,GAAG;GAC/B,eAAe,KAAK;GACpB,OAAO;EACT;EACA,MAAM,KAAK,QACT,YAAY,OAAO,wBAAwB,KAAK,CAAC,GACjD,WAAW,MAAM,EAAE,GACnB,WAAW,MAAM,MAAM,GACvB,WAAW,MAAM,GAAG,CACtB;EACA,IAAI,IACF,aAAa,KAAK;OAElB,eAAe,KAAK;EAEtB,OAAO;CACT;AACF;AAEA,MAAM,yBAAkD,gBAAgB,IAAI,QAAQ,QAAQ;CAC1F,IAAI;EAEF,IAAI,CADW,OAAO,cACZ,CAAC,CAAC,OAAO,MAAM,MAAM,SAAS,GAAG,EAAE,GAC3C,OAAO;EAET,OAAO,QAAQ,OAAO,KAAK,IAAI,MAAM;CACvC,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,MAAa,cAA6B,oBAAoB,qBAAqB"}
|
package/dist/wasm.browser.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { s as WasmPoisonedError } from "./error-BquXDmIN.mjs";
|
|
2
|
-
import { n as NostrWasm, t as LoadNostrWasmOptions } from "./instance-
|
|
2
|
+
import { n as NostrWasm, t as LoadNostrWasmOptions } from "./instance-X_WkCnpR.mjs";
|
|
3
3
|
//#region src/wasm/load.browser.d.ts
|
|
4
4
|
/** Instantiate once. Repeats reuse the same module. Failure throws; no noble fallback. */
|
|
5
5
|
export declare function loadNostrWasm(opts?: LoadNostrWasmOptions): Promise<NostrWasm>;
|
package/dist/wasm.browser.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { b as WasmPoisonedError, m as CryptoError } from "./util-DMF9AIO0.mjs";
|
|
2
|
-
import { n as fetchWasmUrl, r as isWasmBytes, t as createNostrWasmLoader } from "./instance-
|
|
2
|
+
import { n as fetchWasmUrl, r as isWasmBytes, t as createNostrWasmLoader } from "./instance-CI_IaZpf.mjs";
|
|
3
3
|
//#region src/wasm/load.browser.ts
|
|
4
4
|
async function wasmBytes(opts) {
|
|
5
5
|
const source = opts?.module;
|
package/dist/wasm.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { s as WasmPoisonedError } from "./error-BquXDmIN.mjs";
|
|
2
|
-
import { n as NostrWasm, t as LoadNostrWasmOptions } from "./instance-
|
|
2
|
+
import { n as NostrWasm, t as LoadNostrWasmOptions } from "./instance-X_WkCnpR.mjs";
|
|
3
3
|
//#region src/wasm/load.d.ts
|
|
4
4
|
/** Instantiate once. Repeats reuse the same module. Failure throws; no noble fallback. */
|
|
5
5
|
export declare function loadNostrWasm(opts?: LoadNostrWasmOptions): Promise<NostrWasm>;
|
package/dist/wasm.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { b as WasmPoisonedError } from "./util-DMF9AIO0.mjs";
|
|
2
|
-
import { n as fetchWasmUrl, r as isWasmBytes, t as createNostrWasmLoader } from "./instance-
|
|
2
|
+
import { n as fetchWasmUrl, r as isWasmBytes, t as createNostrWasmLoader } from "./instance-CI_IaZpf.mjs";
|
|
3
3
|
//#region src/wasm/load.ts
|
|
4
4
|
async function defaultWasmHref() {
|
|
5
5
|
return (await import("./nostr_crypto_wasm_bg-Czrdty7Q.mjs")).default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qntx/nostr",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Nostr protocol implementation and SDK in TypeScript",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"decentralized",
|
|
@@ -215,10 +215,10 @@
|
|
|
215
215
|
"prepare": "vp config"
|
|
216
216
|
},
|
|
217
217
|
"dependencies": {
|
|
218
|
-
"@noble/ciphers": "2.4.0",
|
|
219
|
-
"@noble/curves": "2.4.0",
|
|
220
|
-
"@noble/hashes": "2.4.0",
|
|
221
|
-
"@scure/base": "2.4.0"
|
|
218
|
+
"@noble/ciphers": "^2.4.0",
|
|
219
|
+
"@noble/curves": "^2.4.0",
|
|
220
|
+
"@noble/hashes": "^2.4.0",
|
|
221
|
+
"@scure/base": "^2.4.0"
|
|
222
222
|
},
|
|
223
223
|
"devDependencies": {
|
|
224
224
|
"@arethetypeswrong/cli": "^0.18.5",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"client-Cj2lUaVh.mjs","names":[],"sources":["../src/client/dm.ts","../src/client/types.ts","../src/client/gossip-io.ts","../src/client/sync.ts","../src/client/client.ts"],"sourcesContent":["import { onAbort, throwIfAborted } from \"../core/abort.ts\";\nimport type { EventBuilder } from \"../core/builder.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { itemCompare } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { SerialQueue } from \"../core/serial.ts\";\nimport type { Gossip } from \"../gossip/gossip.ts\";\nimport {\n Nip17Error,\n buildChatMessageRumor,\n dmRelayListEventBuilder,\n normalizeRecipients,\n requireDmRelays,\n wrapDirectMessage,\n} from \"../nips/nip17.ts\";\nimport type { Recipient } from \"../nips/nip17.ts\";\nimport { unwrap } from \"../nips/nip59.ts\";\nimport type { Nip59Crypto } from \"../nips/nip59.ts\";\nimport type { Pool, PoolPublishResult } from \"../relay/pool.ts\";\nimport type { Closer } from \"../relay/subscription.ts\";\nimport type {\n FetchPrivateMessagesOptions,\n PrivateMessageSendResult,\n PublishOptions,\n ReceivedPrivateMessage,\n SendPrivateMessageOptions,\n SubscribePrivateMessagesOptions,\n} from \"./types.ts\";\n\nexport type DmDeps = {\n pool: Pool;\n gossip: Gossip;\n hydrateGossip: (pubkeys: ReadonlyArray<string>) => Promise<void>;\n ingest: (event: Event, relayUrl?: string) => void;\n markSeen: (id: string, relayUrl: string) => void;\n assertAlive: () => void;\n requireNip59Crypto: () => Nip59Crypto;\n publish: (\n eventOrBuilder: Event | EventBuilder,\n opts?: PublishOptions,\n ) => Promise<PoolPublishResult[]>;\n};\n\nexport function giftWrapRelays(gossip: Gossip, event: Event): ReadonlyArray<string> {\n const targets: string[] = [];\n for (const tag of event.tags) {\n if (tag[0] === \"p\" && tag[1] !== undefined && tag[1] !== \"\") {\n targets.push(tag[1].toLowerCase());\n }\n }\n if (targets.length === 0) {\n throw new Nip17Error(\"gift wrap has no p-tag recipient\");\n }\n const [target] = targets;\n if (targets.length !== 1 || target === undefined) {\n throw new Nip17Error(\"gift wrap must have exactly one p-tag recipient\");\n }\n const relays = gossip.dmRelays(target);\n if (relays.length === 0) {\n throw new Nip17Error(\"no kind 10050 in gossip; use sendPrivateMessage or pass relays\");\n }\n return relays;\n}\n\nexport async function setDmRelays(\n deps: DmDeps,\n relays: ReadonlyArray<string>,\n opts?: PublishOptions,\n): Promise<PoolPublishResult[]> {\n return deps.publish(dmRelayListEventBuilder(relays), { gossip: true, ...opts });\n}\n\nexport async function sendPrivateMessage(\n deps: DmDeps,\n recipients: string | Recipient | ReadonlyArray<string | Recipient>,\n content: string,\n opts?: SendPrivateMessageOptions,\n): Promise<PrivateMessageSendResult> {\n deps.assertAlive();\n const crypto = deps.requireNip59Crypto();\n const sender = await crypto.getPublicKey();\n const list = normalizeRecipients(recipients);\n if (list.length === 0) {\n throw new Nip17Error(\"recipients must not be empty\");\n }\n\n const targets = new Set<string>([sender.toLowerCase(), ...list.map((r) => r.pubkey)]);\n await deps.hydrateGossip([...targets]);\n for (const pk of targets) {\n requireDmRelays(pk, deps.gossip.dmRelays(pk));\n }\n\n const rumor = buildChatMessageRumor(sender, list, content, {\n created_at: opts?.created_at,\n subject: opts?.subject,\n replyTo: opts?.replyTo,\n });\n const wraps = await wrapDirectMessage(crypto, list, rumor, {\n expiration: opts?.expiration,\n ephemeral: opts?.ephemeral,\n });\n const sent = await Promise.all(\n wraps.map(async ({ recipient, wrap }) => {\n const relays = requireDmRelays(recipient, deps.gossip.dmRelays(recipient));\n const results = await deps.pool.publish(relays, wrap, { timeoutMs: opts?.timeoutMs });\n if (results.some((r) => r.status === \"ok\") && opts?.observe !== false) {\n deps.ingest(wrap);\n }\n return { recipient, wrap, results };\n }),\n );\n return { rumor, wraps: sent };\n}\n\nexport async function fetchPrivateMessages(\n deps: DmDeps,\n opts?: FetchPrivateMessagesOptions,\n): Promise<ReceivedPrivateMessage[]> {\n deps.assertAlive();\n const crypto = deps.requireNip59Crypto();\n const self = await crypto.getPublicKey();\n throwIfAborted(opts?.signal);\n await deps.hydrateGossip([self]);\n throwIfAborted(opts?.signal);\n const relays = requireDmRelays(self, deps.gossip.dmRelays(self));\n const urls = new Map<string, Set<string>>();\n const events = await deps.pool.fetch(\n relays,\n [\n {\n kinds: [Kind.GiftWrap],\n \"#p\": [self],\n since: opts?.since,\n until: opts?.until,\n },\n ],\n {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n onevent: (event, relayUrl) => {\n let set = urls.get(event.id);\n if (!set) {\n urls.set(event.id, (set = new Set()));\n }\n set.add(relayUrl);\n },\n },\n );\n\n const byRumor = new Map<string, ReceivedPrivateMessage>();\n for (const wrap of events) {\n try {\n // oxlint-disable-next-line no-await-in-loop -- unwraps run serially so ingest/seenOn keep relay order\n const rumor = await unwrap(crypto, wrap);\n const wrapUrls = urls.get(wrap.id);\n const firstUrl = wrapUrls?.values().next().value;\n if (opts?.observe !== false) {\n deps.ingest(wrap, firstUrl);\n // Every other relay that delivered the same wrap is recorded too.\n if (wrapUrls) {\n for (const url of wrapUrls) {\n if (url !== firstUrl) {\n deps.markSeen(wrap.id, url);\n }\n }\n }\n }\n byRumor.set(rumor.id, {\n wrap,\n rumor,\n ...(firstUrl === undefined ? {} : { relayUrl: firstUrl }),\n });\n } catch {\n // junk / forgery / key mismatch — not stored\n }\n }\n\n return [...byRumor.values()].sort((a, b) => itemCompare(a.rumor, b.rumor));\n}\n\nexport async function subscribePrivateMessages(\n deps: DmDeps,\n opts?: SubscribePrivateMessagesOptions,\n): Promise<Closer> {\n deps.assertAlive();\n const crypto = deps.requireNip59Crypto();\n const self = await crypto.getPublicKey();\n throwIfAborted(opts?.signal);\n await deps.hydrateGossip([self]);\n throwIfAborted(opts?.signal);\n const relays = requireDmRelays(self, deps.gossip.dmRelays(self));\n\n const seen = new Set<string>();\n // Wrap ids processed (observed) already; receipts before that are buffered\n // so every delivering relay URL lands in seenOn once the wrap is indexed.\n const processed = new Set<string>();\n const pendingUrls = new Map<string, string[]>();\n const flushSeen = (wrapId: string): void => {\n processed.add(wrapId);\n const extra = pendingUrls.get(wrapId);\n if (extra === undefined) {\n return;\n }\n pendingUrls.delete(wrapId);\n for (const url of extra) {\n deps.markSeen(wrapId, url);\n }\n };\n const queue = new SerialQueue();\n let closed = false;\n let disposeAbort: (() => void) | undefined;\n const markClosed = (): void => {\n closed = true;\n disposeAbort?.();\n };\n if (opts?.signal?.aborted === true) {\n markClosed();\n } else {\n disposeAbort = onAbort(opts?.signal, markClosed);\n }\n\n const inner = deps.pool.subscribe(\n relays,\n // 21059 is ephemeral (relays MUST NOT store); live inbox has to REQ it.\n [{ kinds: [Kind.GiftWrap, Kind.GiftWrapEphemeral], \"#p\": [self], since: opts?.since }],\n {\n signal: opts?.signal,\n oneose: opts?.oneose,\n onclose: (reason) => {\n markClosed();\n opts?.onclose?.(reason);\n },\n eoseTimeoutMs: opts?.eoseTimeoutMs,\n receivedEvent: (id, relayUrl) => {\n if (closed) {\n return;\n }\n if (processed.has(id)) {\n deps.markSeen(id, relayUrl);\n return;\n }\n const list = pendingUrls.get(id);\n if (list === undefined) {\n pendingUrls.set(id, [relayUrl]);\n } else if (!list.includes(relayUrl)) {\n list.push(relayUrl);\n }\n },\n onevent: (wrap, relayUrl) => {\n if (closed) {\n return;\n }\n void queue.run(async () => {\n try {\n if (closed) {\n return;\n }\n const rumor = await unwrap(crypto, wrap);\n if (closed) {\n return;\n }\n if (seen.has(rumor.id)) {\n flushSeen(wrap.id);\n return;\n }\n seen.add(rumor.id);\n if (opts?.observe !== false) {\n deps.ingest(wrap, relayUrl);\n }\n flushSeen(wrap.id);\n opts?.onevent?.({ wrap, rumor, relayUrl });\n } catch {\n // junk / forgery — not stored; keep the queue alive if a handler throws\n }\n });\n },\n },\n );\n\n return {\n close: (reason?: string) => {\n markClosed();\n inner.close(reason);\n },\n };\n}\n","import { NostrError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Gossip } from \"../gossip/gossip.ts\";\nimport type { ReplyTo } from \"../nips/nip17.ts\";\nimport type { Rumor } from \"../nips/nip59.ts\";\nimport type { PoolOptions, PoolPublishResult } from \"../relay/pool.ts\";\nimport type { NostrSigner } from \"../signer/types.ts\";\nimport type { StorageError } from \"../storage/error.ts\";\nimport type { EventStore } from \"../storage/types.ts\";\nimport type { ReactiveEventStore } from \"../store/reactive.ts\";\n\n/** Direction of a {@link Client.sync} run: upload, download, or both. */\nexport const SyncDirection = {\n Up: \"up\",\n Down: \"down\",\n Both: \"both\",\n} as const;\n\n/** Union of the {@link SyncDirection} values. */\nexport type SyncDirectionName = (typeof SyncDirection)[keyof typeof SyncDirection];\n\n/** Options for {@link Client.sync} (NIP-77 set reconciliation). */\nexport type SyncOptions = {\n relays?: ReadonlyArray<string> | undefined;\n direction?: SyncDirectionName | undefined;\n /**\n * Wall-clock deadline for the Negentropy reconciliation session (`NEG-OPEN` through `NEG-CLOSE`),\n * in milliseconds. One clock for the whole session — not reset per `NEG-MSG`. Default: the relay\n * `publishTimeoutMs`. Upload/download phases reuse this value as their per-call timeout.\n */\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n dryRun?: boolean | undefined;\n /** When false, skip observe/storage on downloaded events. Default true. */\n observe?: boolean | undefined;\n};\n\n/** Outcome of a {@link Client.sync} run: ids compared, sent, received, and failures. */\nexport type SyncSummary = {\n readonly local: ReadonlyArray<string>;\n readonly remote: ReadonlyArray<string>;\n readonly sent: ReadonlyArray<string>;\n readonly received: ReadonlyArray<string>;\n /** Publish failures by event id: relay messages or thrown-error text. */\n readonly sendFailures: Readonly<Record<string, string>>;\n /** PutMany throws by event id. */\n readonly persistFailures: Readonly<Record<string, Error>>;\n};\n\n/**\n * Options for the {@link Client} constructor. Pool fields are forwarded to {@link Pool}; the\n * client-only fields are documented here.\n */\nexport type ClientOptions = Omit<PoolOptions, \"automaticallyAuth\"> & {\n signer?: NostrSigner | undefined;\n relays?: ReadonlyArray<string> | undefined;\n /**\n * When true (default), answer NIP-42 AUTH automatically. The signer is read at challenge time, so\n * `setSigner()` applies to live connections; challenges received while no signer is set are\n * ignored.\n */\n automaticAuth?: boolean | undefined;\n gossip?: Gossip | undefined;\n /**\n * Local event store. Defaults to {@link MemoryEventStore}. Browser apps that want persistence must\n * pass {@link IndexedDbEventStore} and `await open()`.\n */\n storage?: EventStore | undefined;\n /**\n * Synchronous reactive index that mirrors every ingested event before callbacks and persistence.\n * Defaults to a new {@link ReactiveEventStore}.\n */\n index?: ReactiveEventStore | undefined;\n /**\n * When true (default), every ingested event is written to storage. Set false to disable automatic\n * persistence while keeping the store for manual use.\n */\n persistEvents?: boolean | undefined;\n};\n\n/** Typed event payloads for {@link Client.on}. */\nexport type ClientEventMap = {\n /**\n * Storage I/O failures: live `putMany` flush and `fetchEvents({ localFirst: true })` query. Those\n * paths do not throw.\n */\n storageerror: StorageError;\n};\n\nexport type FetchEventsOptions = {\n relays?: ReadonlyArray<string> | undefined;\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n gossip?: boolean | undefined;\n /** When true, query local storage first and merge with network results. Defaults to false. */\n localFirst?: boolean | undefined;\n /** When false, skip writing fetched events to storage/observe. Default true. */\n observe?: boolean | undefined;\n /**\n * Every event of every relay batch, including duplicates across relays. `relayUrl` is the\n * normalized URL of the relay that delivered it.\n */\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n};\n\n/** Options for {@link Client.fetchEach}: one-shot per-relay fetch, no gossip routing. */\nexport type FetchEachOptions = {\n relays?: ReadonlyArray<string> | undefined;\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n /** When false, skip writing fetched events to storage/observe. Default true. */\n observe?: boolean | undefined;\n};\n\nexport type SubscribeOptions = {\n relays?: ReadonlyArray<string> | undefined;\n /**\n * First receipt of each event only (deduped across relays). `relayUrl` is the normalized URL of\n * the relay that delivered it first.\n */\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n /** Every receipt from every relay, including duplicates skipped by dedupe. */\n receivedEvent?: ((id: string, relayUrl: string) => void) | undefined;\n oneose?: (() => void) | undefined;\n onclose?: ((reason: string) => void) | undefined;\n signal?: AbortSignal | undefined;\n id?: string | undefined;\n /**\n * If set, fire `oneose` once after this many ms if not all relays have EOSEd. Does not close the\n * subscription.\n */\n eoseTimeoutMs?: number | undefined;\n /** Fan out REQs via NIP-65 gossip routes when available. */\n gossip?: boolean | undefined;\n /** When false, skip writing received events to storage/observe. Default true. */\n observe?: boolean | undefined;\n};\n\nexport type PublishOptions = {\n relays?: ReadonlyArray<string> | undefined;\n timeoutMs?: number | undefined;\n gossip?: boolean | undefined;\n /** When false, skip writing the published event to storage/observe. Default true. */\n observe?: boolean | undefined;\n};\n\n/** Options for sending a NIP-17 private message. */\nexport type SendPrivateMessageOptions = {\n readonly subject?: string | undefined;\n readonly replyTo?: ReplyTo | undefined;\n readonly created_at?: number | undefined;\n readonly timeoutMs?: number | undefined;\n readonly observe?: boolean | undefined;\n /** NIP-17 disappearing-message expiry (unix seconds) on the seal and the gift wrap. */\n readonly expiration?: number | undefined;\n /** Wrap in the ephemeral kind 21059 (relays MUST NOT store). */\n readonly ephemeral?: boolean | undefined;\n};\n\n/** Result of sending a NIP-17 DM: the rumor plus per-recipient wrap publish results. */\nexport type PrivateMessageSendResult = {\n readonly rumor: Rumor;\n readonly wraps: ReadonlyArray<{\n readonly recipient: string;\n readonly wrap: Event;\n readonly results: ReadonlyArray<PoolPublishResult>;\n }>;\n};\n\n/** An unwrapped NIP-17 private message: the received gift wrap and its inner rumor. */\nexport type ReceivedPrivateMessage = {\n readonly wrap: Event;\n readonly rumor: Rumor;\n /** Normalized URL of the relay that delivered the wrap, when known. */\n readonly relayUrl?: string | undefined;\n};\n\n/** Options for fetching NIP-17 private-message history. */\nexport type FetchPrivateMessagesOptions = {\n readonly since?: number | undefined;\n readonly until?: number | undefined;\n readonly timeoutMs?: number | undefined;\n readonly signal?: AbortSignal | undefined;\n readonly observe?: boolean | undefined;\n};\n\n/** Options for a live NIP-17 private-message subscription. */\nexport type SubscribePrivateMessagesOptions = {\n readonly since?: number | undefined;\n readonly onevent?: ((msg: ReceivedPrivateMessage) => void) | undefined;\n readonly oneose?: (() => void) | undefined;\n readonly onclose?: ((reason: string) => void) | undefined;\n readonly signal?: AbortSignal | undefined;\n readonly eoseTimeoutMs?: number | undefined;\n readonly observe?: boolean | undefined;\n};\n\n/** Client lifecycle, configuration, or abort failure (not cryptographic). */\nexport class ClientError extends NostrError {\n override name = \"ClientError\";\n}\n","import type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport type { Gossip } from \"../gossip/gossip.ts\";\nimport { fanIn, fetchRouted } from \"../relay/fan-in.ts\";\nimport type { FanInOptions, RoutedJob } from \"../relay/fan-in.ts\";\nimport type { Pool } from \"../relay/pool.ts\";\nimport type { Closer } from \"../relay/subscription.ts\";\nimport { ClientError } from \"./types.ts\";\n\n/** Remainder is one job on defaults; throw before any REQ when defaults are empty. */\nexport function jobsForFilters(\n gossip: Gossip,\n filters: ReadonlyArray<Filter>,\n defaultRelays: () => ReadonlyArray<string>,\n): RoutedJob[] {\n const routed = filters.map((f) => gossip.route(f));\n const needsDefaults = routed.some((r) => r.remainder !== undefined);\n const defaults = needsDefaults ? defaultRelays() : undefined;\n const jobs: RoutedJob[] = [];\n for (const r of routed) {\n for (const [url, sub] of r.perRelay) {\n jobs.push({ urls: [url], filters: [sub] });\n }\n if (r.remainder !== undefined) {\n if (defaults === undefined) {\n throw new ClientError(\"unrouted remainder without default relays\");\n }\n jobs.push({ urls: defaults, filters: [r.remainder] });\n }\n }\n return jobs;\n}\n\nexport async function fetchGossip(\n pool: Pool,\n gossip: Gossip,\n filters: ReadonlyArray<Filter>,\n defaultRelays: () => ReadonlyArray<string>,\n opts?: {\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n },\n): Promise<Event[]> {\n return fetchRouted(pool, jobsForFilters(gossip, filters, defaultRelays), {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n onevent: opts?.onevent,\n });\n}\n\nexport function subscribeGossip(\n pool: Pool,\n gossip: Gossip,\n filters: ReadonlyArray<Filter>,\n defaultRelays: () => ReadonlyArray<string>,\n opts: FanInOptions,\n): Closer {\n const jobs = jobsForFilters(gossip, filters, defaultRelays);\n if (jobs.length === 0) {\n queueMicrotask(() => opts.oneose?.());\n return {\n close: () => {\n /* empty */\n },\n };\n }\n return fanIn(pool, jobs, opts);\n}\n","import { throwIfAborted } from \"../core/abort.ts\";\nimport { errorMessage, toError } from \"../core/error.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { canonicalizeFilter } from \"../core/filter.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { storageFromItems } from \"../nips/nip77.ts\";\nimport type { NegentropyStorageVector } from \"../nips/nip77.ts\";\nimport type { Pool } from \"../relay/pool.ts\";\nimport type { EventStore, PutResult } from \"../storage/types.ts\";\nimport { SyncDirection } from \"./types.ts\";\nimport type { SyncOptions, SyncSummary } from \"./types.ts\";\n\nexport type SyncDeps = {\n pool: Pool;\n storage: EventStore;\n persistEvents: boolean;\n assertAlive: () => void;\n /**\n * The client's single ingest path: index (with relay URL) → meta → persistence. `persist: false`\n * when this function already wrote storage via an awaited `putMany`.\n */\n ingest: (event: Event, relayUrl?: string, opts?: { persist?: boolean }) => void;\n /** Record a relay sighting for an id already in the index. */\n markSeen: (id: string, relayUrl: string) => void;\n defaultRelays: (urls?: ReadonlyArray<string>) => ReadonlyArray<string>;\n};\n\nconst SYNC_ID_BATCH = 100;\nconst SYNC_UPLOAD_CONCURRENCY = 8;\n\nfunction uniqueIds(ids: ReadonlyArray<string>): string[] {\n return [...new Set(ids)];\n}\n\nfunction emptySummary(): SyncSummary {\n return {\n local: [],\n remote: [],\n sent: [],\n received: [],\n sendFailures: {},\n persistFailures: {},\n };\n}\n\nfunction mergeSyncSummary(into: SyncSummary, other: SyncSummary): SyncSummary {\n const sendFailures = { ...into.sendFailures, ...other.sendFailures };\n const persistFailures = { ...into.persistFailures, ...other.persistFailures };\n return {\n local: uniqueIds([...into.local, ...other.local]),\n remote: uniqueIds([...into.remote, ...other.remote]),\n sent: uniqueIds([...into.sent, ...other.sent]),\n received: uniqueIds([...into.received, ...other.received]),\n sendFailures,\n persistFailures,\n };\n}\n\n/**\n * NIP-77 sync against one relay: reconcile, then optionally upload local-only events and/or\n * download remote-only events. `observe: false` skips putMany and ingest; received ids are still\n * listed. `persistEvents: false` skips putMany, still ingests when observe is on.\n */\nexport async function syncToRelay(\n deps: SyncDeps,\n url: string,\n filter: Filter,\n opts?: Omit<SyncOptions, \"relays\">,\n): Promise<SyncSummary> {\n deps.assertAlive();\n throwIfAborted(opts?.signal);\n const direction = opts?.direction ?? SyncDirection.Down;\n const canon = canonicalizeFilter(filter);\n const items = await deps.storage.negentropyItems(canon);\n const storage: NegentropyStorageVector = storageFromItems(items);\n const relay = await deps.pool.ensureRelay(url, { signal: opts?.signal });\n const { have, need } = await relay.negReconcile(canon, storage, {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n\n const sent: string[] = [];\n const received: string[] = [];\n const sendFailures: Record<string, string> = {};\n const persistFailures: Record<string, Error> = {};\n const summary: SyncSummary = {\n local: have,\n remote: need,\n sent,\n received,\n sendFailures,\n persistFailures,\n };\n\n if (opts?.dryRun === true) {\n return summary;\n }\n\n if ((direction === SyncDirection.Up || direction === SyncDirection.Both) && have.length > 0) {\n const found = await deps.storage.query([{ ids: have }]);\n const foundById = new Map(found.map((event) => [event.id, event]));\n for (const id of have) {\n if (!foundById.has(id)) {\n sendFailures[id] = \"event not found in local store\";\n }\n }\n for (let i = 0; i < found.length; i += SYNC_UPLOAD_CONCURRENCY) {\n const chunk = found.slice(i, i + SYNC_UPLOAD_CONCURRENCY);\n // Upload chunks are rate-limited serially.\n // oxlint-disable-next-line no-await-in-loop\n await Promise.all(\n chunk.map(async (event) => {\n try {\n const results = await deps.pool.publish([url], event, {\n timeoutMs: opts?.timeoutMs,\n });\n const ok = results.some((r) => r.status === \"ok\");\n if (ok) {\n sent.push(event.id);\n } else {\n const [first] = results;\n sendFailures[event.id] =\n first === undefined\n ? \"publish failed\"\n : first.status === \"failed\"\n ? errorMessage(first.error)\n : first.message;\n }\n } catch (error) {\n sendFailures[event.id] = errorMessage(error);\n }\n }),\n );\n }\n }\n\n if ((direction === SyncDirection.Down || direction === SyncDirection.Both) && need.length > 0) {\n const shouldObserve = opts?.observe !== false;\n for (let i = 0; i < need.length; i += SYNC_ID_BATCH) {\n const batch = need.slice(i, i + SYNC_ID_BATCH);\n throwIfAborted(opts?.signal);\n // Every relay that delivered an event is recorded once it is ingested.\n const urlsById = new Map<string, string[]>();\n // Id batches are fetched serially so backpressure stays bounded.\n // oxlint-disable-next-line no-await-in-loop\n const events = await deps.pool.fetch([url], [{ ids: batch }], {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n onevent: shouldObserve\n ? (event, relayUrl) => {\n const urls = urlsById.get(event.id);\n if (urls === undefined) {\n urlsById.set(event.id, [relayUrl]);\n } else if (!urls.includes(relayUrl)) {\n urls.push(relayUrl);\n }\n }\n : undefined,\n });\n if (!shouldObserve) {\n for (const event of events) {\n received.push(event.id);\n }\n continue;\n }\n const ingestAll = (event: Event): void => {\n const urls = urlsById.get(event.id) ?? [];\n deps.ingest(event, urls[0], { persist: false });\n for (const url of urls.slice(1)) {\n deps.markSeen(event.id, url);\n }\n };\n if (!deps.persistEvents) {\n for (const event of events) {\n ingestAll(event);\n received.push(event.id);\n }\n continue;\n }\n // The awaited putMany reports persistFailures; accepted events then go\n // through the ingest path with persistence already done.\n let results: PutResult[];\n try {\n // Persisted serially per batch so failures land on the right ids.\n // oxlint-disable-next-line no-await-in-loop\n results = await deps.storage.putMany(events);\n } catch (error) {\n const thrown = toError(error);\n for (const event of events) {\n persistFailures[event.id] = thrown;\n }\n break;\n }\n for (const [j, event] of events.entries()) {\n if (results[j] === \"rejected\" || results[j] === \"invalid\") {\n continue;\n }\n ingestAll(event);\n received.push(event.id);\n }\n }\n }\n\n return summary;\n}\n\n/**\n * NIP-77 sync against the given relays (or Client default relays). Independent sessions run in\n * parallel. Fulfilled summaries are merged; if every relay rejects, throws the first rejection in\n * URL order.\n */\nexport async function sync(\n deps: SyncDeps,\n filter: Filter,\n opts?: SyncOptions,\n): Promise<SyncSummary> {\n deps.assertAlive();\n const urls = deps.defaultRelays(opts?.relays);\n const results = await Promise.allSettled(\n urls.map(async (url) => syncToRelay(deps, url, filter, opts)),\n );\n let merged = emptySummary();\n let fulfilled = 0;\n let firstRejection: unknown;\n for (const result of results) {\n if (result.status === \"fulfilled\") {\n fulfilled += 1;\n merged = mergeSyncSummary(merged, result.value);\n } else {\n firstRejection ??= result.reason;\n }\n }\n if (urls.length > 0 && fulfilled === 0) {\n throw firstRejection;\n }\n return merged;\n}\n","import { EventBuilder } from \"../core/builder.ts\";\nimport { Emitter } from \"../core/emitter.ts\";\nimport { sortedEvents } from \"../core/event.ts\";\nimport type { Event, EventTemplate, UnsignedEvent } from \"../core/event.ts\";\nimport { canonicalizeFilters, matchFilters } from \"../core/filter.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport { Gossip } from \"../gossip/index.ts\";\nimport { createLoaders, OutboxFeed } from \"../loaders/index.ts\";\nimport type { Loaders } from \"../loaders/index.ts\";\nimport type { Recipient } from \"../nips/nip17.ts\";\nimport { isGiftWrapKind, requireNip59Crypto } from \"../nips/nip59.ts\";\nimport type { Nip59Crypto } from \"../nips/nip59.ts\";\nimport { Pool } from \"../relay/pool.ts\";\nimport type { PoolFetchResult, PoolPublishResult } from \"../relay/pool.ts\";\nimport type { Closer } from \"../relay/subscription.ts\";\nimport { NoSignerError } from \"../signer/error.ts\";\nimport type { NostrSigner } from \"../signer/types.ts\";\nimport { toStorageError } from \"../storage/error.ts\";\nimport { MemoryIndex } from \"../storage/memory-index.ts\";\nimport { MemoryEventStore } from \"../storage/memory.ts\";\nimport type { EventStore } from \"../storage/types.ts\";\nimport { ReactiveEventStore } from \"../store/reactive.ts\";\nimport {\n fetchPrivateMessages,\n giftWrapRelays,\n sendPrivateMessage,\n setDmRelays,\n subscribePrivateMessages,\n} from \"./dm.ts\";\nimport type { DmDeps } from \"./dm.ts\";\nimport { fetchGossip, subscribeGossip } from \"./gossip-io.ts\";\nimport { sync, syncToRelay } from \"./sync.ts\";\nimport type { SyncDeps } from \"./sync.ts\";\nimport { ClientError } from \"./types.ts\";\nimport type {\n ClientEventMap,\n ClientOptions,\n FetchEachOptions,\n FetchEventsOptions,\n FetchPrivateMessagesOptions,\n PrivateMessageSendResult,\n PublishOptions,\n ReceivedPrivateMessage,\n SendPrivateMessageOptions,\n SubscribeOptions,\n SubscribePrivateMessagesOptions,\n SyncOptions,\n SyncSummary,\n} from \"./types.ts\";\n\nfunction isFilterList(filter: Filter | ReadonlyArray<Filter>): filter is ReadonlyArray<Filter> {\n return Array.isArray(filter);\n}\n\nfunction toFilterList(filter: Filter | ReadonlyArray<Filter>): Filter[] {\n return canonicalizeFilters(isFilterList(filter) ? filter : [filter]);\n}\n\n/** Layer-5 facade: signer + default relays + pool + loaders + gossip + event store. */\nexport class Client {\n readonly pool: Pool;\n readonly loaders: Loaders;\n readonly gossip: Gossip;\n readonly storage: EventStore;\n readonly index: ReactiveEventStore;\n #signer: NostrSigner | undefined;\n #relays: string[];\n #shutdown = false;\n readonly #persistEvents: boolean;\n readonly #persistQueue: Event[] = [];\n #flushing: Promise<void> | undefined;\n readonly #events = new Emitter<ClientEventMap>();\n readonly #dmDeps: DmDeps;\n readonly #syncDeps: SyncDeps;\n\n constructor(opts: ClientOptions = {}) {\n const { signer, relays, automaticAuth, gossip, storage, index, persistEvents, ...poolOptions } =\n opts;\n this.#signer = signer;\n this.#relays = [];\n for (const raw of relays ?? []) {\n const url = normalizeURL(raw);\n if (!this.#relays.includes(url)) {\n this.#relays.push(url);\n }\n }\n this.gossip = gossip ?? new Gossip();\n this.storage = storage ?? new MemoryEventStore();\n this.index = index ?? new ReactiveEventStore();\n this.#persistEvents = persistEvents ?? true;\n this.pool = new Pool({\n ...poolOptions,\n enableReconnect: poolOptions.enableReconnect ?? true,\n // The sign function resolves the signer at challenge time, so\n // setSigner() takes effect on already-connected relays; challenges\n // without a signer are ignored by the relay (NoSignerError).\n automaticallyAuth:\n (automaticAuth ?? true)\n ? () => async (template) => {\n const signer = this.#signer;\n if (!signer) {\n throw new NoSignerError(\"no signer configured for AUTH\");\n }\n const pk = await signer.getPublicKey();\n return signer.signEvent({ ...template, pubkey: pk });\n }\n : undefined,\n });\n this.loaders = createLoaders({\n pool: this.pool,\n relays: this.#relays,\n index: this.index,\n ingest: (event, relayUrl) => this.#ingest(event, relayUrl),\n });\n this.#dmDeps = {\n pool: this.pool,\n gossip: this.gossip,\n hydrateGossip: async (pubkeys) => this.hydrateGossip(pubkeys),\n ingest: (event, relayUrl) => this.#ingest(event, relayUrl),\n markSeen: (id, relayUrl) => {\n this.index.markSeen(id, relayUrl);\n },\n assertAlive: () => this.#assertAlive(),\n requireNip59Crypto: () => this.#requireNip59Crypto(),\n publish: async (eventOrBuilder, opts) => this.publish(eventOrBuilder, opts),\n };\n this.#syncDeps = {\n pool: this.pool,\n storage: this.storage,\n persistEvents: this.#persistEvents,\n assertAlive: () => this.#assertAlive(),\n ingest: (event, relayUrl, opts) => this.#ingest(event, relayUrl, opts),\n markSeen: (id, relayUrl) => {\n this.index.markSeen(id, relayUrl);\n },\n defaultRelays: (urls) => this.#defaultRelays(urls),\n };\n }\n\n get signer(): NostrSigner | undefined {\n return this.#signer;\n }\n\n get relays(): ReadonlyArray<string> {\n return this.#relays;\n }\n\n get isShutdown(): boolean {\n return this.#shutdown;\n }\n\n setSigner(signer: NostrSigner | undefined): void {\n this.#signer = signer;\n // Re-arm relays whose AUTH challenge was ignored or rejected.\n this.pool.resetAuth();\n }\n\n addRelay(url: string): void {\n const normalized = normalizeURL(url);\n if (!this.#relays.includes(normalized)) {\n this.#relays.push(normalized);\n this.loaders.addRelay(normalized);\n }\n }\n\n removeRelay(url: string): void {\n const normalized = normalizeURL(url);\n this.#relays = this.#relays.filter((r) => r !== normalized);\n this.loaders.removeRelay(normalized);\n this.pool.closeRelays([normalized]);\n }\n\n /** Connect all configured relays (best-effort; failures are ignored). */\n async connect(opts?: { signal?: AbortSignal | undefined }): Promise<void> {\n this.#assertAlive();\n await Promise.allSettled(\n this.#relays.map(async (url) => this.pool.ensureRelay(url, { signal: opts?.signal })),\n );\n }\n\n async shutdown(): Promise<void> {\n this.#shutdown = true;\n while (this.#flushing !== undefined) {\n // oxlint-disable-next-line no-await-in-loop -- waits for the current flush to settle before closing\n await this.#flushing;\n }\n this.pool.close();\n }\n\n /**\n * Listen for a client event. Multiple listeners per type are allowed and fire in registration\n * order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe\n * function.\n */\n on<K extends keyof ClientEventMap>(\n type: K,\n listener: (err: ClientEventMap[K]) => void,\n ): () => void {\n return this.#events.on(type, listener);\n }\n\n #assertAlive(): void {\n if (this.#shutdown) {\n throw new ClientError(\"client is shut down\");\n }\n }\n\n #defaultRelays(urls?: ReadonlyArray<string>): ReadonlyArray<string> {\n const list = urls ?? this.#relays;\n if (list.length === 0) {\n throw new ClientError(\"no relays configured\");\n }\n return list;\n }\n\n /** Ingest one inbound event: index (with relay URL) → meta → persistence. */\n observe(event: Event, relayUrl?: string): void {\n this.#ingest(event, relayUrl);\n }\n\n /** Ingest many events (deduped by id, order preserved) as one persist batch. */\n observeAll(events: ReadonlyArray<Event>): void {\n const seen = new Set<string>();\n for (const event of events) {\n if (seen.has(event.id)) {\n continue;\n }\n seen.add(event.id);\n this.#ingest(event);\n }\n }\n\n /**\n * The single inbound-event pipeline: reactive index add (with the source relay URL recorded in\n * seenOn), gossip routing meta, then the persistence decision. `persist: false` skips the storage\n * queue for callers that persist via their own awaited `putMany` (down-sync); `meta: false` is a\n * pure sighting/index add.\n */\n #ingest(event: Event, relayUrl?: string, opts?: { persist?: boolean; meta?: boolean }): void {\n this.index.add(event, relayUrl);\n if (opts?.meta !== false) {\n this.gossip.ingest(event);\n }\n if (opts?.persist === false || !this.#persistEvents) {\n return;\n }\n this.#persistQueue.push(event);\n this.#armFlush();\n }\n\n #armFlush(): void {\n if (this.#flushing) {\n return;\n }\n this.#flushing = this.#flushLoop();\n }\n\n async #flushLoop(): Promise<void> {\n try {\n await new Promise<void>((resolve) => {\n queueMicrotask(resolve);\n });\n while (this.#persistQueue.length > 0) {\n const batch = this.#persistQueue.splice(0);\n try {\n // oxlint-disable-next-line no-await-in-loop -- queued batches are persisted in order\n await this.storage.putMany(batch);\n } catch (error) {\n this.#events.emit(\"storageerror\", toStorageError(error));\n }\n }\n } finally {\n this.#flushing = undefined;\n if (this.#persistQueue.length > 0) {\n this.#armFlush();\n }\n }\n }\n\n /**\n * Explicitly load kind:10002 and kind:10050 lists for pubkeys and feed gossip. Does not\n * background-fetch unknown authors on every subscribe (predictable DX).\n */\n async hydrateGossip(\n pubkeys: ReadonlyArray<string>,\n opts?: { hints?: string[]; force?: boolean },\n ): Promise<void> {\n this.#assertAlive();\n await Promise.all(\n pubkeys.map(async (pk) => {\n const style = opts?.force === true ? \"force\" : \"default\";\n const [relayList, dmList] = await Promise.all([\n this.loaders.relayList(pk, { hints: opts?.hints, style }),\n this.loaders.dmRelayList(pk, { hints: opts?.hints, style }),\n ]);\n if (relayList.event) {\n this.observe(relayList.event);\n }\n if (dmList.event) {\n this.observe(dmList.event);\n }\n }),\n );\n }\n\n /**\n * Create an outbox-model feed for the given authors (NIP-65 write relays). Live events go through\n * {@link observe}; sync persists via applySync.\n */\n outbox(opts: {\n authors: ReadonlyArray<string>;\n kinds?: ReadonlyArray<number>;\n onEvent?: (event: Event) => void;\n maxRelaysPerAuthor?: number;\n }): OutboxFeed {\n this.#assertAlive();\n return new OutboxFeed({\n pool: this.pool,\n gossip: this.gossip,\n storage: this.storage,\n discoveryRelays: this.#relays,\n authors: opts.authors,\n kinds: opts.kinds,\n onEvent: opts.onEvent,\n maxRelaysPerAuthor: opts.maxRelaysPerAuthor,\n observe: (event, relayUrl) => this.observe(event, relayUrl),\n seen: (event, relayUrl) => this.#ingest(event, relayUrl, { persist: false, meta: false }),\n applySync: async (events) => {\n if (!this.#persistEvents) {\n for (const event of events) {\n this.#ingest(event, undefined, { persist: false });\n }\n return [];\n }\n const results = await this.storage.putMany(events);\n const applied: Event[] = [];\n for (const [i, event] of events.entries()) {\n const result = results[i];\n if (result === \"rejected\" || result === \"ephemeral\" || result === \"invalid\") {\n continue;\n }\n this.#ingest(event, undefined, { persist: false });\n applied.push(event);\n }\n return applied;\n },\n hydrate: async (pubkeys) => this.hydrateGossip(pubkeys),\n });\n }\n\n #requireSigner(): NostrSigner {\n const signer = this.#signer;\n if (!signer) {\n throw new ClientError(\"no signer configured\");\n }\n return signer;\n }\n\n async getPublicKey(): Promise<string> {\n return this.#requireSigner().getPublicKey();\n }\n\n async signEvent(unsigned: UnsignedEvent): Promise<Event> {\n return this.#requireSigner().signEvent(unsigned);\n }\n\n async signEventBuilder(builder: EventBuilder): Promise<Event> {\n return builder.sign(this.#requireSigner());\n }\n\n async signTemplate(template: EventTemplate): Promise<Event> {\n const signer = this.#requireSigner();\n const pubkey = await signer.getPublicKey();\n return signer.signEvent({ ...template, pubkey });\n }\n\n /**\n * Sign (if builder) and publish to relays. With `gossip: true`, prefers the author's NIP-65\n * outbox relays, tagged `p` inboxes, and up to 5 `e`/`a` tag relay hints when known. On any\n * successful OK, observes the event into storage/gossip.\n */\n async publish(\n eventOrBuilder: Event | EventBuilder,\n opts?: PublishOptions,\n ): Promise<PoolPublishResult[]> {\n this.#assertAlive();\n const event =\n eventOrBuilder instanceof EventBuilder\n ? await this.signEventBuilder(eventOrBuilder)\n : eventOrBuilder;\n\n let relays = opts?.relays;\n if (!relays && isGiftWrapKind(event.kind)) {\n relays = giftWrapRelays(this.gossip, event);\n } else if (!relays && opts?.gossip === true) {\n const urls: string[] = [];\n const add = (list: ReadonlyArray<string>) => {\n for (const url of list) {\n if (!urls.includes(url)) {\n urls.push(url);\n }\n }\n };\n add(this.gossip.outboxRelays(event.pubkey));\n for (const tag of event.tags) {\n if (tag[0] === \"p\" && tag[1] !== undefined) {\n add(this.gossip.inboxRelays(tag[1]));\n }\n }\n let hintCount = 0;\n for (const tag of event.tags) {\n if (hintCount >= 5) {\n break;\n }\n if ((tag[0] !== \"e\" && tag[0] !== \"a\") || tag[2] === undefined) {\n continue;\n }\n try {\n const url = normalizeURL(tag[2]);\n if (!urls.includes(url)) {\n urls.push(url);\n hintCount += 1;\n }\n } catch {\n // not a relay URL\n }\n }\n if (urls.length > 0) {\n relays = urls;\n }\n }\n const results = await this.pool.publish(this.#defaultRelays(relays), event, {\n timeoutMs: opts?.timeoutMs,\n });\n\n const anyOk = results.some((r) => r.status === \"ok\");\n if (anyOk && opts?.observe !== false) {\n this.observe(event);\n }\n return results;\n }\n\n /**\n * Fetch events. With `gossip: true`, routes filters by NIP-65 when possible. With `localFirst:\n * true`, merges storage hits with network results.\n */\n async fetchEvents(\n filter: Filter | ReadonlyArray<Filter>,\n opts?: FetchEventsOptions,\n ): Promise<Event[]> {\n this.#assertAlive();\n const filters = toFilterList(filter);\n const shouldObserve = opts?.observe !== false;\n const candidates: Event[] = [];\n\n if (opts?.localFirst === true) {\n // Persisted storage is merged with the index below; storage may hold a\n // stale replaceable version, so it is only a candidate — the index wins.\n try {\n const local = await this.storage.query(filters);\n for (const e of local) {\n candidates.push(e);\n if (shouldObserve) {\n this.#ingest(e, undefined, { persist: false, meta: false });\n }\n }\n } catch (error) {\n this.#events.emit(\"storageerror\", toStorageError(error));\n }\n }\n\n // Every inbound event lands in the index (with its relay URL) before the\n // caller's onevent runs; the batch below re-ingests for persistence.\n const onevent = (event: Event, relayUrl: string) => {\n if (shouldObserve) {\n this.#ingest(event, relayUrl, { persist: false, meta: false });\n }\n opts?.onevent?.(event, relayUrl);\n };\n\n const batch =\n opts?.gossip !== true || opts.relays !== undefined\n ? await this.pool.fetch(this.#defaultRelays(opts?.relays), filters, {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n onevent,\n })\n : await fetchGossip(this.pool, this.gossip, filters, () => this.#defaultRelays(), {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n onevent,\n });\n for (const e of batch) {\n candidates.push(e);\n if (shouldObserve) {\n this.observe(e);\n }\n }\n\n // Merge through a scratch index so NIP-01 replaceable winners, kind-5\n // deletions, dedupe and per-filter limits all apply to the result.\n // Ephemeral kinds are never stored; matching ones join the result directly.\n const tmp = new MemoryIndex();\n const ephemeral = new Map<string, Event>();\n const merged =\n opts?.localFirst === true ? [...this.index.query(filters), ...candidates] : candidates;\n for (const e of merged) {\n if (tmp.put(e) === \"ephemeral\" && matchFilters(filters, e)) {\n ephemeral.set(e.id, e);\n }\n }\n return sortedEvents([...tmp.query(filters), ...ephemeral.values()]);\n }\n\n /**\n * One-shot fetch with per-relay results: each entry reports its events and end reason (`eose`,\n * `closed`, `timeout`, or `failed` when the relay could not be reached). Events are ingested into\n * the index with each relay's URL in `seenOn` and persisted exactly like {@link fetchEvents}\n * (`observe` defaults to true). No gossip routing and no local merge.\n */\n async fetchEach(\n filter: Filter | ReadonlyArray<Filter>,\n opts?: FetchEachOptions,\n ): Promise<PoolFetchResult[]> {\n this.#assertAlive();\n const filters = toFilterList(filter);\n const results = await this.pool.fetchEach(this.#defaultRelays(opts?.relays), filters, {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n if (opts?.observe !== false) {\n const unique: Event[] = [];\n const seen = new Set<string>();\n for (const result of results) {\n for (const event of result.events) {\n this.#ingest(event, result.url, { persist: false, meta: false });\n if (!seen.has(event.id)) {\n seen.add(event.id);\n unique.push(event);\n }\n }\n }\n for (const event of unique) {\n this.observe(event);\n }\n }\n return results;\n }\n\n /** Query local storage only (no network). */\n async queryLocal(filter: Filter | ReadonlyArray<Filter>): Promise<Event[]> {\n this.#assertAlive();\n const filters = toFilterList(filter);\n return this.storage.query(filters);\n }\n\n subscribe(filter: Filter | ReadonlyArray<Filter>, opts?: SubscribeOptions): Closer {\n this.#assertAlive();\n const filters = toFilterList(filter);\n const shouldObserve = opts?.observe !== false;\n\n const wrapEvent = (event: Event, relayUrl: string) => {\n if (shouldObserve) {\n this.observe(event, relayUrl);\n }\n opts?.onevent?.(event, relayUrl);\n };\n\n const wrapReceived = (id: string, relayUrl: string) => {\n if (shouldObserve) {\n this.index.markSeen(id, relayUrl);\n }\n opts?.receivedEvent?.(id, relayUrl);\n };\n\n if (opts?.gossip !== true || opts.relays !== undefined) {\n return this.pool.subscribe(this.#defaultRelays(opts?.relays), filters, {\n onevent: wrapEvent,\n receivedEvent: wrapReceived,\n oneose: opts?.oneose,\n onclose: opts?.onclose,\n signal: opts?.signal,\n id: opts?.id,\n eoseTimeoutMs: opts?.eoseTimeoutMs,\n });\n }\n\n return subscribeGossip(this.pool, this.gossip, filters, () => this.#defaultRelays(), {\n onevent: wrapEvent,\n receivedEvent: wrapReceived,\n oneose: opts?.oneose,\n onclose: opts?.onclose,\n signal: opts?.signal,\n eoseTimeoutMs: opts?.eoseTimeoutMs,\n });\n }\n\n #requireNip59Crypto(): Nip59Crypto {\n return requireNip59Crypto(this.#requireSigner());\n }\n\n /** Publish a kind:10050 NIP-17 DM relay list (`relay` tags). Not kind 10002. */\n async setDmRelays(\n relays: ReadonlyArray<string>,\n opts?: PublishOptions,\n ): Promise<PoolPublishResult[]> {\n return setDmRelays(this.#dmDeps, relays, opts);\n }\n\n async sendPrivateMessage(\n recipients: string | Recipient | ReadonlyArray<string | Recipient>,\n content: string,\n opts?: SendPrivateMessageOptions,\n ): Promise<PrivateMessageSendResult> {\n return sendPrivateMessage(this.#dmDeps, recipients, content, opts);\n }\n\n async fetchPrivateMessages(\n opts?: FetchPrivateMessagesOptions,\n ): Promise<ReceivedPrivateMessage[]> {\n return fetchPrivateMessages(this.#dmDeps, opts);\n }\n\n async subscribePrivateMessages(opts?: SubscribePrivateMessagesOptions): Promise<Closer> {\n return subscribePrivateMessages(this.#dmDeps, opts);\n }\n\n /**\n * NIP-77 sync against one relay: reconcile, then optionally upload local-only events and/or\n * download remote-only events. `observe: false` skips putMany and ingest; received ids are still\n * listed. `persistEvents: false` skips putMany, still ingests when observe is on.\n */\n async syncToRelay(\n url: string,\n filter: Filter,\n opts?: Omit<SyncOptions, \"relays\">,\n ): Promise<SyncSummary> {\n return syncToRelay(this.#syncDeps, url, filter, opts);\n }\n\n /**\n * NIP-77 sync against the given relays (or Client default relays). Independent sessions run in\n * parallel. Fulfilled summaries are merged; if every relay rejects, throws the first rejection in\n * URL order.\n */\n async sync(filter: Filter, opts?: SyncOptions): Promise<SyncSummary> {\n return sync(this.#syncDeps, filter, opts);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"event-CHf47dPM.mjs","names":[],"sources":["../src/core/tag.ts","../src/core/event.ts"],"sourcesContent":["import { isAddressableKind, isReplaceableKind } from \"./kind.ts\";\nimport { isHex32 } from \"./util.ts\";\n\n/** A NIP-01 tag: first element is the name, rest are values. */\nexport type Tag = ReadonlyArray<string>;\n\n/** Mutable tag builder input. */\nexport type TagInput = string[];\n\n/** Type guard: a non-empty array of strings. */\nexport function isTag(value: unknown): value is Tag {\n if (!Array.isArray(value) || value.length === 0) {\n return false;\n }\n for (const item of value) {\n if (typeof item !== \"string\") {\n return false;\n }\n }\n return true;\n}\n\n/** First element of a tag (its name). */\nexport function tagName(tag: Tag): string {\n return tag[0] ?? \"\";\n}\n\n/** Second element of a tag (its primary value), or undefined for a bare name tag. */\nexport function tagValue(tag: Tag): string | undefined {\n return tag[1];\n}\n\n/** Construct common tags. */\nexport const Tag = {\n e(id: string, relay?: string, marker?: string, pubkey?: string): Tag {\n const t: string[] = [\"e\", id.toLowerCase()];\n if (relay !== undefined) {\n t.push(relay);\n }\n if (marker !== undefined) {\n t.push(marker);\n }\n if (pubkey !== undefined) {\n t.push(pubkey.toLowerCase());\n }\n return t;\n },\n p(pubkey: string, relay?: string, petname?: string): Tag {\n const t: string[] = [\"p\", pubkey.toLowerCase()];\n if (relay !== undefined) {\n t.push(relay);\n }\n if (petname !== undefined) {\n t.push(petname);\n }\n return t;\n },\n a(coordinate: string, relay?: string): Tag {\n const t: string[] = [\"a\", coordinate];\n if (relay !== undefined) {\n t.push(relay);\n }\n return t;\n },\n d(identifier: string): Tag {\n return [\"d\", identifier];\n },\n t(hashtag: string): Tag {\n return [\"t\", hashtag];\n },\n r(url: string, marker?: string): Tag {\n const t: string[] = [\"r\", url];\n if (marker !== undefined) {\n t.push(marker);\n }\n return t;\n },\n k(kind: number | string): Tag {\n return [\"k\", String(kind)];\n },\n} as const;\n\n/** First tag value with the given name, or undefined when no such tag has a value. */\nexport function firstTagValue(tags: ReadonlyArray<Tag>, name: string): string | undefined {\n for (const tag of tags) {\n if (tag[0] === name && tag[1] !== undefined) {\n return tag[1];\n }\n }\n return undefined;\n}\n\n/** First `d` tag value on an event, if any. */\nexport function getDTag(tags: ReadonlyArray<Tag>): string | undefined {\n return firstTagValue(tags, \"d\");\n}\n\n/** NIP-01 `a` tag: `kind:pubkey:identifier` (identifier may be empty and may contain extra colons). */\nexport type EventAddress = {\n kind: number;\n pubkey: string;\n identifier: string;\n};\n\n/** Parse a NIP-01 addressable/replaceable coordinate. */\nexport function parseEventAddress(value: string): EventAddress | undefined {\n const first = value.indexOf(\":\");\n if (first <= 0) {\n return undefined;\n }\n const second = value.indexOf(\":\", first + 1);\n if (second === -1) {\n return undefined;\n }\n const kind = Number(value.slice(0, first));\n if (!Number.isInteger(kind) || kind < 0 || kind > 65535) {\n return undefined;\n }\n const pubkey = value.slice(first + 1, second).toLowerCase();\n if (!isHex32(pubkey)) {\n return undefined;\n }\n return { kind, pubkey, identifier: value.slice(second + 1) };\n}\n\n/** Format an address as a `kind:pubkey:dTag` coordinate string. */\nexport function formatEventAddress(kind: number, pubkey: string, identifier = \"\"): string {\n return `${kind}:${pubkey.toLowerCase()}:${identifier}`;\n}\n\n/**\n * Coordinate for replaceable (`kind:pubkey:`) and addressable (`kind:pubkey:d`) events. Undefined\n * for regular/ephemeral kinds.\n */\nexport function eventAddress(event: {\n kind: number;\n pubkey: string;\n tags: ReadonlyArray<Tag>;\n}): string | undefined {\n if (isAddressableKind(event.kind)) {\n return formatEventAddress(event.kind, event.pubkey, getDTag(event.tags) ?? \"\");\n }\n if (isReplaceableKind(event.kind)) {\n return formatEventAddress(event.kind, event.pubkey, \"\");\n }\n return undefined;\n}\n","import { sha256 } from \"@noble/hashes/sha2.js\";\n\nimport { EventValidationError } from \"./error.ts\";\nimport type { Tag } from \"./tag.ts\";\nimport { isTag } from \"./tag.ts\";\nimport { isHex32, isHex64, isRecord, utf8Encoder, bytesToHex } from \"./util.ts\";\n\n/** Wire event template before pubkey/id/sig. */\nexport type EventTemplate = {\n readonly kind: number;\n readonly tags: ReadonlyArray<Tag>;\n readonly content: string;\n readonly created_at: number;\n};\n\n/** Event with pubkey, ready to hash and sign. */\nexport type UnsignedEvent = EventTemplate & {\n readonly pubkey: string;\n};\n\n/**\n * Fully formed NIP-01 event (JSON-friendly plain object). Fields are readonly at the type level;\n * wire interop remains plain JSON.\n */\nexport type Event = UnsignedEvent & {\n readonly id: string;\n readonly sig: string;\n};\n\n/** Cache of events whose signatures have been verified successfully. */\nconst verifiedEvents = new WeakSet<Event>();\n\n/** Cache of events known to fail verification. */\nconst failedEvents = new WeakSet<Event>();\n\nexport function markVerified(event: Event): void {\n verifiedEvents.add(event);\n failedEvents.delete(event);\n}\n\nexport function markUnverified(event: Event): void {\n failedEvents.add(event);\n verifiedEvents.delete(event);\n}\n\nexport function isMarkedVerified(event: Event): boolean {\n return verifiedEvents.has(event);\n}\n\nexport function isMarkedFailed(event: Event): boolean {\n return failedEvents.has(event);\n}\n\n/** Structural validation of an unsigned event (no crypto). */\nexport function validateEvent(event: unknown): event is UnsignedEvent {\n if (!isRecord(event)) {\n return false;\n }\n if (\n typeof event[\"kind\"] !== \"number\" ||\n !Number.isInteger(event[\"kind\"]) ||\n event[\"kind\"] < 0 ||\n event[\"kind\"] > 65535\n ) {\n return false;\n }\n if (typeof event[\"content\"] !== \"string\") {\n return false;\n }\n if (typeof event[\"created_at\"] !== \"number\" || !Number.isInteger(event[\"created_at\"])) {\n return false;\n }\n if (typeof event[\"pubkey\"] !== \"string\" || !isHex32(event[\"pubkey\"])) {\n return false;\n }\n if (!Array.isArray(event[\"tags\"])) {\n return false;\n }\n for (const tag of event[\"tags\"]) {\n if (!isTag(tag)) {\n return false;\n }\n }\n return true;\n}\n\n/** Structural validation of a full event including id and sig hex shape. */\nexport function validateSignedEvent(event: unknown): event is Event {\n if (!validateEvent(event)) {\n return false;\n }\n const e = event as Record<string, unknown>;\n if (typeof e[\"id\"] !== \"string\" || !isHex32(e[\"id\"])) {\n return false;\n }\n if (typeof e[\"sig\"] !== \"string\" || !isHex64(e[\"sig\"])) {\n return false;\n }\n return true;\n}\n\n/**\n * Canonical NIP-01 serialization used for hashing: JSON array `[0, pubkey, created_at, kind, tags,\n * content]` with no extra whitespace.\n */\nexport function serializeEvent(event: UnsignedEvent): string {\n if (!validateEvent(event)) {\n throw new EventValidationError(\"cannot serialize event with invalid shape\");\n }\n return JSON.stringify([0, event.pubkey, event.created_at, event.kind, event.tags, event.content]);\n}\n\n/** SHA-256 of the canonical serialization, lowercase hex. */\nexport function getEventHash(event: UnsignedEvent): string {\n const serialized = serializeEvent(event);\n return bytesToHex(sha256(utf8Encoder.encode(serialized)));\n}\n\n/** Id tie-break: plain code-unit order (ids are canonical lowercase hex, so `<` is lexical). */\nfunction compareIds(a: { id: string }, b: { id: string }): number {\n if (a.id < b.id) {\n return -1;\n }\n if (a.id > b.id) {\n return 1;\n }\n return 0;\n}\n\n/**\n * NIP-01 newest-first order: `created_at` descending, then `id` in plain code-unit order (ids are\n * canonical lowercase hex, so `<` is lexical order).\n */\nexport function compareEventsDesc(\n a: { id: string; created_at: number },\n b: { id: string; created_at: number },\n): number {\n if (a.created_at !== b.created_at) {\n return b.created_at - a.created_at;\n }\n return compareIds(a, b);\n}\n\n/**\n * Sort events reverse-chronologically by `created_at`, then by `id` lexicographically. Mutates and\n * returns the array.\n */\nexport function sortEvents(events: Event[]): Event[] {\n return events.sort(compareEventsDesc);\n}\n\n/** Non-mutating sort; returns a new array. */\nexport function sortedEvents(events: ReadonlyArray<Event>): Event[] {\n return [...events].sort(compareEventsDesc);\n}\n\n/**\n * Negentropy item order: `created_at` ascending, then `id` lexicographic (`<`/`>`). Time axis is\n * opposite {@link sortEvents}; id tie-break is the same direction.\n */\nexport function itemCompare(\n a: { id: string; created_at: number },\n b: { id: string; created_at: number },\n): number {\n if (a.created_at !== b.created_at) {\n return a.created_at - b.created_at;\n }\n return compareIds(a, b);\n}\n\n/**\n * NIP-01 replaceable/addressable winner: higher `created_at`, or equal timestamp and\n * lexicographically lower `id`.\n */\nexport function isReplaceableWinner(\n candidate: { created_at: number; id: string },\n incumbent: { created_at: number; id: string },\n): boolean {\n if (candidate.created_at !== incumbent.created_at) {\n return candidate.created_at > incumbent.created_at;\n }\n return candidate.id < incumbent.id;\n}\n\n/** True when a signed event is the signed form of `unsigned` (kind/tags/content/time/pubkey). */\nexport function signedMatchesUnsigned(signed: Event, unsigned: UnsignedEvent): boolean {\n if (signed.kind !== unsigned.kind) {\n return false;\n }\n if (signed.content !== unsigned.content) {\n return false;\n }\n if (signed.created_at !== unsigned.created_at) {\n return false;\n }\n if (JSON.stringify(signed.tags) !== JSON.stringify(unsigned.tags)) {\n return false;\n }\n if (unsigned.pubkey && signed.pubkey.toLowerCase() !== unsigned.pubkey.toLowerCase()) {\n return false;\n }\n return true;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"filter-BQl3GlHt.mjs","names":[],"sources":["../src/core/filter.ts"],"sourcesContent":["import type { Event } from \"./event.ts\";\nimport { isAddressableKind, isReplaceableKind } from \"./kind.ts\";\n\n/** NIP-01 filter object (immutable-friendly; callers may still pass plain objects). */\nexport type Filter = {\n readonly ids?: ReadonlyArray<string> | undefined;\n readonly kinds?: ReadonlyArray<number> | undefined;\n readonly authors?: ReadonlyArray<string> | undefined;\n readonly since?: number | undefined;\n readonly until?: number | undefined;\n readonly limit?: number | undefined;\n /** NIP-50 full-text search. Relays interpret this. Local `matchFilter` / `query` ignore it. */\n readonly search?: string | undefined;\n readonly [key: `#${string}`]: ReadonlyArray<string> | undefined;\n};\n\n/** Local NIP-01 match. `search` is ignored; relays interpret NIP-50. */\nexport function matchFilter(filter: Filter, event: Event): boolean {\n if (filter.ids && !filter.ids.some((id) => id.toLowerCase() === event.id)) {\n return false;\n }\n if (filter.kinds && !filter.kinds.includes(event.kind)) {\n return false;\n }\n if (filter.authors && !filter.authors.some((pk) => pk.toLowerCase() === event.pubkey)) {\n return false;\n }\n\n for (const key of Object.keys(filter)) {\n if (!key.startsWith(\"#\")) {\n continue;\n }\n const tagName = key.slice(1);\n const values = filter[`#${tagName}`];\n if (!values) {\n continue;\n }\n const hexTag = tagName === \"e\" || tagName === \"p\";\n const hit = event.tags.some((tag) => {\n const [tagKey, tagValue] = tag;\n if (tagKey !== tagName || tagValue === undefined) {\n return false;\n }\n if (hexTag) {\n return values.some((v) => v.toLowerCase() === tagValue.toLowerCase());\n }\n return values.includes(tagValue);\n });\n if (!hit) {\n return false;\n }\n }\n\n if (filter.since !== undefined && event.created_at < filter.since) {\n return false;\n }\n if (filter.until !== undefined && event.created_at > filter.until) {\n return false;\n }\n\n return true;\n}\n\n/** True when the event matches any of the filters (NIP-01 OR semantics). */\nexport function matchFilters(filters: ReadonlyArray<Filter>, event: Event): boolean {\n for (const filter of filters) {\n if (matchFilter(filter, event)) {\n return true;\n }\n }\n return false;\n}\n\n/** Merge filters by unioning list fields; returns a new plain object. */\nexport function mergeFilters(...filters: Filter[]): Filter {\n const result: Record<string, unknown> = {};\n const mergeList = (property: string, values: ReadonlyArray<unknown> | undefined): void => {\n if (values === undefined) {\n return;\n }\n const existing = result[property];\n const list: unknown[] = Array.isArray(existing) ? [...(existing as unknown[])] : [];\n for (const value of values) {\n if (!list.includes(value)) {\n list.push(value);\n }\n }\n result[property] = list;\n };\n const mergeMax = (property: \"limit\" | \"until\", value: number | undefined): void => {\n if (value === undefined) {\n return;\n }\n const prev = result[property];\n if (typeof prev !== \"number\" || value > prev) {\n result[property] = value;\n }\n };\n for (const filter of filters) {\n mergeList(\"ids\", filter.ids);\n mergeList(\"authors\", filter.authors);\n mergeList(\"kinds\", filter.kinds);\n for (const key of Object.keys(filter)) {\n if (!key.startsWith(\"#\")) {\n continue;\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- startsWith(\"#\") above guarantees the template key\n mergeList(key, filter[key as `#${string}`]);\n }\n mergeMax(\"limit\", filter.limit);\n mergeMax(\"until\", filter.until);\n if (filter.since !== undefined) {\n const prev = result[\"since\"];\n if (typeof prev !== \"number\" || filter.since < prev) {\n result[\"since\"] = filter.since;\n }\n }\n if (filter.search !== undefined) {\n result[\"search\"] = filter.search;\n }\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- built key by key above; the record is filter-shaped by construction\n return result as Filter;\n}\n\n/**\n * Intrinsic upper bound implied by the filter alone. Returns a positive integer, or `Infinity` when\n * unbounded.\n */\nexport function getFilterLimit(filter: Filter): number {\n if (filter.ids && filter.ids.length === 0) {\n return 0;\n }\n if (filter.kinds && filter.kinds.length === 0) {\n return 0;\n }\n if (filter.authors && filter.authors.length === 0) {\n return 0;\n }\n\n let limit = Number.POSITIVE_INFINITY;\n if (filter.ids) {\n limit = Math.min(limit, filter.ids.length);\n }\n if (filter.limit !== undefined) {\n limit = Math.min(limit, filter.limit);\n }\n\n if (filter.kinds && filter.authors) {\n const allReplaceable = filter.kinds.every((k) => isReplaceableKind(k) || isAddressableKind(k));\n if (allReplaceable) {\n const dTags = filter[\"#d\"];\n const perAuthor = dTags && dTags.length > 0 ? dTags.length : 1;\n limit = Math.min(limit, filter.authors.length * filter.kinds.length * perAuthor);\n }\n }\n\n return limit;\n}\n\nconst HEX_LIST_KEYS = new Set([\"ids\", \"authors\", \"#e\", \"#p\"]);\n\n/** Lowercase hex lists and sort every array. Omits undefined so `[]` stays distinct from missing. */\nexport function canonicalizeFilter(filter: Filter): Filter {\n const raw = filter as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(raw).sort()) {\n const value = raw[key];\n // omit undefined so a missing key is not `[]` / null\n if (value === undefined) {\n continue;\n }\n if (Array.isArray(value)) {\n if (HEX_LIST_KEYS.has(key)) {\n out[key] = value.map((v) => String(v).toLowerCase()).sort();\n } else if (key === \"kinds\") {\n out[key] = value.map(Number).sort((a, b) => a - b);\n } else {\n out[key] = value.map(String).sort();\n }\n } else {\n out[key] = value;\n }\n }\n // oxlint-disable-next-line typescript/no-unsafe-type-assertion -- built key by key above; the record is filter-shaped by construction\n return out as Filter;\n}\n\n/** Apply {@link canonicalizeFilter} to each filter. */\nexport function canonicalizeFilters(filters: ReadonlyArray<Filter>): Filter[] {\n return filters.map(canonicalizeFilter);\n}\n\n/**\n * Canonical identity for live REQ coalescing. Relays treat a missing list key as unconstrained and\n * `[]` as match-nothing.\n */\nexport function filterFingerprint(filters: ReadonlyArray<Filter>): string {\n const parts = filters.map((filter) => JSON.stringify(canonicalizeFilter(filter)));\n parts.sort();\n return `[${parts.join(\",\")}]`;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"gossip-DFxIBlsr.mjs","names":[],"sources":["../src/gossip/gossip.ts"],"sourcesContent":["import { touchKey, trimOldest } from \"../core/collections.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport { isReplaceableWinner } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { normalizeRelayUrls, normalizeURL, nowSeconds } from \"../core/util.ts\";\nimport { parseDmRelayList } from \"../nips/nip17.ts\";\nimport { parseRelayList } from \"../nips/nip65.ts\";\nimport type { RelayListItem } from \"../nips/nip65.ts\";\n\n/** Relay routing state for one pubkey: NIP-65 outbox/inbox plus NIP-17 DM relays. */\nexport type PubkeyRoutes = {\n /** Relays the user writes to (outbox). NIP-65. */\n readonly write: ReadonlyArray<string>;\n /** Relays the user reads from (inbox). NIP-65. */\n readonly read: ReadonlyArray<string>;\n /** Relays for NIP-17 gift-wrap delivery. Kind 10050. */\n readonly dm: ReadonlyArray<string>;\n /** `created_at` of the last accepted kind:10002 list. */\n readonly updatedAt: number;\n /** `created_at` of the last accepted kind:10050 list. */\n readonly dmUpdatedAt: number;\n /** Event id of the last accepted kind:10002 (NIP-01 equal-timestamp tie-break). */\n readonly relayListId?: string | undefined;\n /** Event id of the last accepted kind:10050. */\n readonly dmListId?: string | undefined;\n};\n\n/** A filter split by gossip routes: per-relay narrowed filters plus the unrouted remainder. */\nexport type RoutedFilter = {\n /** Url → already-narrowed filter. Empty when nothing routed. */\n readonly perRelay: ReadonlyMap<string, Filter>;\n /**\n * Unrouted work for the caller:\n *\n * - No authors and no #p: original filter (\"generic\")\n * - Authors/#p all unrouted: original filter (\"orphan\")\n * - Mixed: narrowed leftover (\"fallback\")\n * - All routed: undefined\n */\n readonly remainder?: Filter | undefined;\n};\n\nexport type GossipOptions = {\n /** Max relays to keep per direction when ranking. Default 4. */\n maxRelaysPerPubkey?: number | undefined;\n /**\n * Max pubkeys with tracked routes; the map is LRU — writes and lookups refresh recency, and\n * inserting beyond the cap drops the oldest entry. Default 10_000.\n */\n maxPubkeys?: number | undefined;\n};\n\nfunction emptyRoutes(): PubkeyRoutes {\n return {\n write: [],\n read: [],\n dm: [],\n updatedAt: 0,\n dmUpdatedAt: 0,\n };\n}\n\n/**\n * Routing table for NIP-65 (10002) and NIP-17 DM relays (10050). Ingest replaceable list events,\n * then route filters into per-relay REQs.\n */\nexport class Gossip {\n readonly #routes = new Map<string, PubkeyRoutes>();\n readonly #maxRelays: number;\n readonly #maxPubkeys: number;\n\n constructor(opts: GossipOptions = {}) {\n this.#maxRelays = opts.maxRelaysPerPubkey ?? 4;\n this.#maxPubkeys = opts.maxPubkeys ?? 10_000;\n }\n\n /**\n * Ingest a routing list event.\n *\n * - Kind:10002 → write/read (NIP-65)\n * - Kind:10050 → dm (NIP-17)\n *\n * Returns true if routes for that list type were updated.\n */\n ingest(event: Event): boolean {\n if (event.kind === Kind.RelayList) {\n let items: RelayListItem[];\n try {\n items = parseRelayList(event);\n } catch {\n return false;\n }\n return this.setRoutes(event.pubkey, items, event.created_at, event.id);\n }\n if (event.kind === Kind.DirectMessageRelaysList) {\n let relays: string[];\n try {\n relays = parseDmRelayList(event);\n } catch {\n return false;\n }\n return this.setDmRoutes(event.pubkey, relays, event.created_at, event.id);\n }\n return false;\n }\n\n setRoutes(\n pubkey: string,\n items: ReadonlyArray<RelayListItem>,\n updatedAt: number = nowSeconds(),\n eventId?: string,\n ): boolean {\n const pk = pubkey.toLowerCase();\n const prev = this.#lookup(pk) ?? emptyRoutes();\n if (prev.updatedAt > updatedAt) {\n return false;\n }\n if (\n eventId !== undefined &&\n prev.relayListId !== undefined &&\n prev.updatedAt === updatedAt &&\n !isReplaceableWinner(\n { created_at: updatedAt, id: eventId },\n { created_at: prev.updatedAt, id: prev.relayListId },\n )\n ) {\n return false;\n }\n\n const write: string[] = [];\n const read: string[] = [];\n for (const item of items) {\n let url: string;\n try {\n url = normalizeURL(item.url);\n } catch {\n continue;\n }\n if (item.marker !== \"read\" && !write.includes(url)) {\n write.push(url);\n }\n if (item.marker !== \"write\" && !read.includes(url)) {\n read.push(url);\n }\n }\n\n this.#put(pk, {\n write: write.slice(0, this.#maxRelays),\n read: read.slice(0, this.#maxRelays),\n dm: prev.dm,\n updatedAt,\n dmUpdatedAt: prev.dmUpdatedAt,\n relayListId: eventId ?? prev.relayListId,\n dmListId: prev.dmListId,\n });\n return true;\n }\n\n setDmRoutes(\n pubkey: string,\n relays: ReadonlyArray<string>,\n updatedAt: number = nowSeconds(),\n eventId?: string,\n ): boolean {\n const pk = pubkey.toLowerCase();\n const prev = this.#lookup(pk) ?? emptyRoutes();\n if (prev.dmUpdatedAt > updatedAt) {\n return false;\n }\n if (\n eventId !== undefined &&\n prev.dmListId !== undefined &&\n prev.dmUpdatedAt === updatedAt &&\n !isReplaceableWinner(\n { created_at: updatedAt, id: eventId },\n { created_at: prev.dmUpdatedAt, id: prev.dmListId },\n )\n ) {\n return false;\n }\n\n const dm = normalizeRelayUrls(relays);\n\n this.#put(pk, {\n write: prev.write,\n read: prev.read,\n dm: dm.slice(0, this.#maxRelays),\n updatedAt: prev.updatedAt,\n dmUpdatedAt: updatedAt,\n relayListId: prev.relayListId,\n dmListId: eventId ?? prev.dmListId,\n });\n return true;\n }\n\n getRoutes(pubkey: string): Readonly<PubkeyRoutes> | undefined {\n return this.#lookup(pubkey.toLowerCase());\n }\n\n outboxRelays(pubkey: string): ReadonlyArray<string> {\n return this.#lookup(pubkey.toLowerCase())?.write ?? [];\n }\n\n inboxRelays(pubkey: string): ReadonlyArray<string> {\n return this.#lookup(pubkey.toLowerCase())?.read ?? [];\n }\n\n /** NIP-17 kind:10050 delivery relays for gift-wraps. */\n dmRelays(pubkey: string): ReadonlyArray<string> {\n return this.#lookup(pubkey.toLowerCase())?.dm ?? [];\n }\n\n clear(pubkey?: string): void {\n if (pubkey !== undefined && pubkey !== \"\") {\n this.#routes.delete(pubkey.toLowerCase());\n } else {\n this.#routes.clear();\n }\n }\n\n get size(): number {\n return this.#routes.size;\n }\n\n /**\n * Route a user-facing filter into per-relay sub-filters plus leftover.\n *\n * - Authors → outbox relays, filter narrowed per relay's authors\n * - #p only → inbox relays of those pubkeys\n * - Neither / all unrouted → remainder is the original filter\n * - Mixed → remainder is the narrowed leftover; all routed → no remainder\n */\n route(filter: Filter): RoutedFilter {\n const authors = filter.authors?.map((a) => a.toLowerCase());\n const pTags = filter[\"#p\"]?.map((p) => p.toLowerCase());\n\n if ((!authors || authors.length === 0) && (!pTags || pTags.length === 0)) {\n return { perRelay: new Map(), remainder: filter };\n }\n\n if (authors && authors.length > 0 && (!pTags || pTags.length === 0)) {\n return this.#breakAuthors(filter, authors, \"write\");\n }\n\n if (pTags && pTags.length > 0 && (!authors || authors.length === 0)) {\n const { authors: _authors, ...base } = filter;\n return this.#breakAuthors(base, pTags, \"read\", (b, pubkeys) => ({ ...b, \"#p\": pubkeys }));\n }\n\n // both authors and #p: send full filter to union of routes\n const unionKeys = new Set([...(authors ?? []), ...(pTags ?? [])]);\n const relays = new Set<string>();\n let anyUnrouted = false;\n for (const pk of unionKeys) {\n const r = this.#lookup(pk);\n const urls = r ? [...r.write, ...r.read] : [];\n if (urls.length === 0) {\n anyUnrouted = true;\n continue;\n }\n for (const u of urls) {\n relays.add(u);\n }\n }\n if (relays.size === 0) {\n return { perRelay: new Map(), remainder: filter };\n }\n const map = new Map<string, Filter>();\n for (const url of relays) {\n map.set(url, { ...filter });\n }\n return anyUnrouted ? { perRelay: map, remainder: filter } : { perRelay: map };\n }\n\n #breakAuthors(\n filter: Filter,\n pubkeys: string[],\n direction: \"read\" | \"write\",\n narrow: (base: Filter, pubkeys: string[]) => Filter = (base, pks) => ({\n ...base,\n authors: pks,\n }),\n ): RoutedFilter {\n const perRelay = new Map<string, string[]>();\n const unrouted: string[] = [];\n let anyRoute = false;\n\n for (const pk of pubkeys) {\n const routes = this.#lookup(pk);\n const urls = direction === \"write\" ? routes?.write : routes?.read;\n if (!urls || urls.length === 0) {\n unrouted.push(pk);\n continue;\n }\n anyRoute = true;\n for (const url of urls) {\n const list = perRelay.get(url) ?? [];\n list.push(pk);\n perRelay.set(url, list);\n }\n }\n\n if (!anyRoute) {\n return { perRelay: new Map(), remainder: filter };\n }\n\n const map = new Map<string, Filter>();\n for (const [url, pks] of perRelay) {\n map.set(url, narrow(filter, pks));\n }\n return unrouted.length > 0\n ? { perRelay: map, remainder: narrow(filter, unrouted) }\n : { perRelay: map };\n }\n\n /** Read that refreshes LRU recency. */\n #lookup(pk: string): PubkeyRoutes | undefined {\n const routes = this.#routes.get(pk);\n if (routes !== undefined) {\n touchKey(this.#routes, pk, routes);\n }\n return routes;\n }\n\n /** Write at the newest LRU end, then trim the oldest beyond the cap. */\n #put(pk: string, routes: PubkeyRoutes): void {\n touchKey(this.#routes, pk, routes);\n trimOldest(this.#routes, this.#maxPubkeys);\n }\n}\n"],"mappings":";;;;;;;AAqDA,SAAS,cAA4B;CACnC,OAAO;EACL,OAAO,CAAC;EACR,MAAM,CAAC;EACP,IAAI,CAAC;EACL,WAAW;EACX,aAAa;CACf;AACF;;;;;AAMA,IAAa,SAAb,MAAoB;CAClB,0BAAmB,IAAI,IAA0B;CACjD;CACA;CAEA,YAAY,OAAsB,CAAC,GAAG;EACpC,KAAK,aAAa,KAAK,sBAAsB;EAC7C,KAAK,cAAc,KAAK,cAAc;CACxC;;;;;;;;;CAUA,OAAO,OAAuB;EAC5B,IAAI,MAAM,SAAS,KAAK,WAAW;GACjC,IAAI;GACJ,IAAI;IACF,QAAQ,eAAe,KAAK;GAC9B,QAAQ;IACN,OAAO;GACT;GACA,OAAO,KAAK,UAAU,MAAM,QAAQ,OAAO,MAAM,YAAY,MAAM,EAAE;EACvE;EACA,IAAI,MAAM,SAAS,KAAK,yBAAyB;GAC/C,IAAI;GACJ,IAAI;IACF,SAAS,iBAAiB,KAAK;GACjC,QAAQ;IACN,OAAO;GACT;GACA,OAAO,KAAK,YAAY,MAAM,QAAQ,QAAQ,MAAM,YAAY,MAAM,EAAE;EAC1E;EACA,OAAO;CACT;CAEA,UACE,QACA,OACA,YAAoB,WAAW,GAC/B,SACS;EACT,MAAM,KAAK,OAAO,YAAY;EAC9B,MAAM,OAAO,KAAK,QAAQ,EAAE,KAAK,YAAY;EAC7C,IAAI,KAAK,YAAY,WACnB,OAAO;EAET,IACE,YAAY,KAAA,KACZ,KAAK,gBAAgB,KAAA,KACrB,KAAK,cAAc,aACnB,CAAC,oBACC;GAAE,YAAY;GAAW,IAAI;EAAQ,GACrC;GAAE,YAAY,KAAK;GAAW,IAAI,KAAK;EAAY,CACrD,GAEA,OAAO;EAGT,MAAM,QAAkB,CAAC;EACzB,MAAM,OAAiB,CAAC;EACxB,KAAK,MAAM,QAAQ,OAAO;GACxB,IAAI;GACJ,IAAI;IACF,MAAM,aAAa,KAAK,GAAG;GAC7B,QAAQ;IACN;GACF;GACA,IAAI,KAAK,WAAW,UAAU,CAAC,MAAM,SAAS,GAAG,GAC/C,MAAM,KAAK,GAAG;GAEhB,IAAI,KAAK,WAAW,WAAW,CAAC,KAAK,SAAS,GAAG,GAC/C,KAAK,KAAK,GAAG;EAEjB;EAEA,KAAK,KAAK,IAAI;GACZ,OAAO,MAAM,MAAM,GAAG,KAAK,UAAU;GACrC,MAAM,KAAK,MAAM,GAAG,KAAK,UAAU;GACnC,IAAI,KAAK;GACT;GACA,aAAa,KAAK;GAClB,aAAa,WAAW,KAAK;GAC7B,UAAU,KAAK;EACjB,CAAC;EACD,OAAO;CACT;CAEA,YACE,QACA,QACA,YAAoB,WAAW,GAC/B,SACS;EACT,MAAM,KAAK,OAAO,YAAY;EAC9B,MAAM,OAAO,KAAK,QAAQ,EAAE,KAAK,YAAY;EAC7C,IAAI,KAAK,cAAc,WACrB,OAAO;EAET,IACE,YAAY,KAAA,KACZ,KAAK,aAAa,KAAA,KAClB,KAAK,gBAAgB,aACrB,CAAC,oBACC;GAAE,YAAY;GAAW,IAAI;EAAQ,GACrC;GAAE,YAAY,KAAK;GAAa,IAAI,KAAK;EAAS,CACpD,GAEA,OAAO;EAGT,MAAM,KAAK,mBAAmB,MAAM;EAEpC,KAAK,KAAK,IAAI;GACZ,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,IAAI,GAAG,MAAM,GAAG,KAAK,UAAU;GAC/B,WAAW,KAAK;GAChB,aAAa;GACb,aAAa,KAAK;GAClB,UAAU,WAAW,KAAK;EAC5B,CAAC;EACD,OAAO;CACT;CAEA,UAAU,QAAoD;EAC5D,OAAO,KAAK,QAAQ,OAAO,YAAY,CAAC;CAC1C;CAEA,aAAa,QAAuC;EAClD,OAAO,KAAK,QAAQ,OAAO,YAAY,CAAC,CAAC,EAAE,SAAS,CAAC;CACvD;CAEA,YAAY,QAAuC;EACjD,OAAO,KAAK,QAAQ,OAAO,YAAY,CAAC,CAAC,EAAE,QAAQ,CAAC;CACtD;;CAGA,SAAS,QAAuC;EAC9C,OAAO,KAAK,QAAQ,OAAO,YAAY,CAAC,CAAC,EAAE,MAAM,CAAC;CACpD;CAEA,MAAM,QAAuB;EAC3B,IAAI,WAAW,KAAA,KAAa,WAAW,IACrC,KAAK,QAAQ,OAAO,OAAO,YAAY,CAAC;OAExC,KAAK,QAAQ,MAAM;CAEvB;CAEA,IAAI,OAAe;EACjB,OAAO,KAAK,QAAQ;CACtB;;;;;;;;;CAUA,MAAM,QAA8B;EAClC,MAAM,UAAU,OAAO,SAAS,KAAK,MAAM,EAAE,YAAY,CAAC;EAC1D,MAAM,QAAQ,OAAO,KAAK,EAAE,KAAK,MAAM,EAAE,YAAY,CAAC;EAEtD,KAAK,CAAC,WAAW,QAAQ,WAAW,OAAO,CAAC,SAAS,MAAM,WAAW,IACpE,OAAO;GAAE,0BAAU,IAAI,IAAI;GAAG,WAAW;EAAO;EAGlD,IAAI,WAAW,QAAQ,SAAS,MAAM,CAAC,SAAS,MAAM,WAAW,IAC/D,OAAO,KAAK,cAAc,QAAQ,SAAS,OAAO;EAGpD,IAAI,SAAS,MAAM,SAAS,MAAM,CAAC,WAAW,QAAQ,WAAW,IAAI;GACnE,MAAM,EAAE,SAAS,UAAU,GAAG,SAAS;GACvC,OAAO,KAAK,cAAc,MAAM,OAAO,SAAS,GAAG,aAAa;IAAE,GAAG;IAAG,MAAM;GAAQ,EAAE;EAC1F;EAGA,MAAM,4BAAY,IAAI,IAAI,CAAC,GAAI,WAAW,CAAC,GAAI,GAAI,SAAS,CAAC,CAAE,CAAC;EAChE,MAAM,yBAAS,IAAI,IAAY;EAC/B,IAAI,cAAc;EAClB,KAAK,MAAM,MAAM,WAAW;GAC1B,MAAM,IAAI,KAAK,QAAQ,EAAE;GACzB,MAAM,OAAO,IAAI,CAAC,GAAG,EAAE,OAAO,GAAG,EAAE,IAAI,IAAI,CAAC;GAC5C,IAAI,KAAK,WAAW,GAAG;IACrB,cAAc;IACd;GACF;GACA,KAAK,MAAM,KAAK,MACd,OAAO,IAAI,CAAC;EAEhB;EACA,IAAI,OAAO,SAAS,GAClB,OAAO;GAAE,0BAAU,IAAI,IAAI;GAAG,WAAW;EAAO;EAElD,MAAM,sBAAM,IAAI,IAAoB;EACpC,KAAK,MAAM,OAAO,QAChB,IAAI,IAAI,KAAK,EAAE,GAAG,OAAO,CAAC;EAE5B,OAAO,cAAc;GAAE,UAAU;GAAK,WAAW;EAAO,IAAI,EAAE,UAAU,IAAI;CAC9E;CAEA,cACE,QACA,SACA,WACA,UAAuD,MAAM,SAAS;EACpE,GAAG;EACH,SAAS;CACX,IACc;EACd,MAAM,2BAAW,IAAI,IAAsB;EAC3C,MAAM,WAAqB,CAAC;EAC5B,IAAI,WAAW;EAEf,KAAK,MAAM,MAAM,SAAS;GACxB,MAAM,SAAS,KAAK,QAAQ,EAAE;GAC9B,MAAM,OAAO,cAAc,UAAU,QAAQ,QAAQ,QAAQ;GAC7D,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;IAC9B,SAAS,KAAK,EAAE;IAChB;GACF;GACA,WAAW;GACX,KAAK,MAAM,OAAO,MAAM;IACtB,MAAM,OAAO,SAAS,IAAI,GAAG,KAAK,CAAC;IACnC,KAAK,KAAK,EAAE;IACZ,SAAS,IAAI,KAAK,IAAI;GACxB;EACF;EAEA,IAAI,CAAC,UACH,OAAO;GAAE,0BAAU,IAAI,IAAI;GAAG,WAAW;EAAO;EAGlD,MAAM,sBAAM,IAAI,IAAoB;EACpC,KAAK,MAAM,CAAC,KAAK,QAAQ,UACvB,IAAI,IAAI,KAAK,OAAO,QAAQ,GAAG,CAAC;EAElC,OAAO,SAAS,SAAS,IACrB;GAAE,UAAU;GAAK,WAAW,OAAO,QAAQ,QAAQ;EAAE,IACrD,EAAE,UAAU,IAAI;CACtB;;CAGA,QAAQ,IAAsC;EAC5C,MAAM,SAAS,KAAK,QAAQ,IAAI,EAAE;EAClC,IAAI,WAAW,KAAA,GACb,SAAS,KAAK,SAAS,IAAI,MAAM;EAEnC,OAAO;CACT;;CAGA,KAAK,IAAY,QAA4B;EAC3C,SAAS,KAAK,SAAS,IAAI,MAAM;EACjC,WAAW,KAAK,SAAS,KAAK,WAAW;CAC3C;AACF"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"key-DnVpchl6.mjs","names":["hexToBytes","bytesToHex"],"sources":["../src/core/key.ts"],"sourcesContent":["import { schnorr } from \"@noble/curves/secp256k1.js\";\nimport { randomBytes } from \"@noble/hashes/utils.js\";\n\nimport { CryptoError, EventValidationError } from \"./error.ts\";\nimport { getEventHash, markVerified, validateEvent } from \"./event.ts\";\nimport type { Event, EventTemplate, UnsignedEvent } from \"./event.ts\";\nimport { assertHex32, assertSecretKeyBytes, bytesToHex, hexToBytes } from \"./util.ts\";\n\n/** 32-byte secret key held as bytes; prefer zeroize when done. */\nexport class SecretKey {\n #bytes: Uint8Array | undefined;\n\n private constructor(bytes: Uint8Array) {\n assertSecretKeyBytes(bytes);\n this.#bytes = new Uint8Array(bytes);\n }\n\n static generate(): SecretKey {\n return new SecretKey(schnorr.utils.randomSecretKey());\n }\n\n static fromBytes(bytes: Uint8Array): SecretKey {\n return new SecretKey(bytes);\n }\n\n static fromHex(hex: string): SecretKey {\n return new SecretKey(hexToBytes(assertHex32(hex, \"secret key\")));\n }\n\n get bytes(): Uint8Array {\n if (!this.#bytes) {\n throw new CryptoError(\"secret key has been zeroized\");\n }\n return new Uint8Array(this.#bytes);\n }\n\n toHex(): string {\n return bytesToHex(this.bytes);\n }\n\n /** Best-effort wipe of the internal buffer. */\n zeroize(): void {\n if (this.#bytes) {\n this.#bytes.fill(0);\n this.#bytes = undefined;\n }\n }\n}\n\n/** Anything a secret key can be given as: instance, raw bytes, or hex. */\nexport type SecretKeyInput = SecretKey | Uint8Array | string;\n\n/** Coerce a {@link SecretKeyInput} to {@link SecretKey}. */\nexport function toSecretKey(input: SecretKeyInput): SecretKey {\n if (input instanceof SecretKey) {\n return input;\n }\n if (typeof input === \"string\") {\n return SecretKey.fromHex(input);\n }\n return SecretKey.fromBytes(input);\n}\n\n/** Lowercase hex-encoded secp256k1 public key (x-only, 64 chars). */\nexport type PublicKey = string;\n\n/** Validate and normalize a hex public key; throws {@link HexError} on bad input. */\nexport function publicKeyFromHex(hex: string): PublicKey {\n return assertHex32(hex, \"public key\");\n}\n\n/**\n * BIP-340 signing backend. `publicKey` returns the 32-byte x-only public key of a 32-byte secret\n * key; `sign` returns the 64-byte BIP-340 signature of a 32-byte `id` using 32 bytes of auxiliary\n * randomness. Both throw on invalid input (for example an out-of-range secret key).\n */\nexport type SigningBackend = {\n publicKey: (secretKey: Uint8Array) => Uint8Array;\n sign: (id: Uint8Array, secretKey: Uint8Array, auxRand: Uint8Array) => Uint8Array;\n};\n\nconst nobleSigning: SigningBackend = {\n publicKey: (secretKey) => schnorr.getPublicKey(secretKey),\n sign: (id, secretKey, auxRand) => schnorr.sign(id, secretKey, auxRand),\n};\n\n/** Derive the public key for a secret key given as SecretKey, bytes, or hex. */\nexport function getPublicKey(secretKey: SecretKeyInput): PublicKey {\n return bytesToHex(schnorr.getPublicKey(toSecretKey(secretKey).bytes));\n}\n\n/** Keypair convenience wrapper. */\nexport class Keys {\n readonly secretKey: SecretKey;\n readonly publicKey: PublicKey;\n /**\n * BIP-340 backend that derived {@link publicKey} and signs in {@link finalizeEvent}/\n * {@link signEvent}. Defaults to noble; pass a loaded `NostrWasm` module or a native backend to\n * delegate signing.\n */\n readonly backend: SigningBackend;\n\n private constructor(secretKey: SecretKey, backend: SigningBackend) {\n this.secretKey = secretKey;\n this.backend = backend;\n const pubkey = backend.publicKey(secretKey.bytes);\n if (pubkey.length !== 32) {\n throw new CryptoError(\"signing backend returned an invalid public key\");\n }\n this.publicKey = bytesToHex(pubkey);\n }\n\n static generate(backend: SigningBackend = nobleSigning): Keys {\n return new Keys(SecretKey.generate(), backend);\n }\n\n static fromSecretKey(secretKey: SecretKeyInput, backend: SigningBackend = nobleSigning): Keys {\n return new Keys(toSecretKey(secretKey), backend);\n }\n}\n\nfunction resolveKeys(secretKey: SecretKeyInput | Keys): Keys {\n return secretKey instanceof Keys ? secretKey : Keys.fromSecretKey(secretKey);\n}\n\n/** Fill pubkey/id/sig on a template and return a signed event. */\nexport function finalizeEvent(template: EventTemplate, secretKey: SecretKeyInput | Keys): Event {\n const keys = resolveKeys(secretKey);\n const unsigned: UnsignedEvent = {\n kind: template.kind,\n tags: template.tags,\n content: template.content,\n created_at: template.created_at,\n pubkey: keys.publicKey,\n };\n return signEvent(unsigned, keys);\n}\n\n/**\n * Sign an already-assembled unsigned event. Rejects when `unsigned.pubkey` does not match the\n * secret key.\n */\nexport function signEvent(unsigned: UnsignedEvent, secretKey: SecretKeyInput | Keys): Event {\n const keys = resolveKeys(secretKey);\n if (!validateEvent(unsigned)) {\n throw new EventValidationError(\"cannot sign invalid unsigned event\");\n }\n\n if (unsigned.pubkey !== keys.publicKey) {\n throw new CryptoError(\"unsigned event pubkey does not match secret key\");\n }\n\n const normalized: UnsignedEvent = {\n kind: unsigned.kind,\n tags: unsigned.tags,\n content: unsigned.content,\n created_at: unsigned.created_at,\n pubkey: keys.publicKey,\n };\n\n const id = getEventHash(normalized);\n const sig = keys.backend.sign(hexToBytes(id), keys.secretKey.bytes, randomBytes(32));\n if (sig.length !== 64) {\n throw new CryptoError(\"signing backend returned an invalid signature\");\n }\n const event: Event = { ...normalized, id, sig: bytesToHex(sig) };\n markVerified(event);\n return event;\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kind-Cxwhhkx8.mjs","names":[],"sources":["../src/core/kind.ts"],"sourcesContent":["/** NIP-01 regular: stored by relays. */\nexport function isRegularKind(kind: number): boolean {\n return kind === 1 || kind === 2 || (kind >= 4 && kind < 45) || (kind >= 1000 && kind < 10000);\n}\n\n/** Replaceable: latest per (pubkey, kind) wins. */\nexport function isReplaceableKind(kind: number): boolean {\n return kind === 0 || kind === 3 || (kind >= 10000 && kind < 20000);\n}\n\n/** Ephemeral: not expected to be stored. */\nexport function isEphemeralKind(kind: number): boolean {\n return kind >= 20000 && kind < 30000;\n}\n\n/** Addressable (parameterized replaceable): latest per (pubkey, kind, d-tag) wins. */\nexport function isAddressableKind(kind: number): boolean {\n return kind >= 30000 && kind < 40000;\n}\n\n/** NIP-01 kind classes: regular / replaceable / ephemeral / addressable. */\nexport type KindClassification =\n | \"regular\"\n | \"replaceable\"\n | \"ephemeral\"\n | \"addressable\"\n | \"unknown\";\n\n/** Classify a kind number into its NIP-01 storage class (`\"unknown\"` outside all ranges). */\nexport function classifyKind(kind: number): KindClassification {\n if (isRegularKind(kind)) {\n return \"regular\";\n }\n if (isReplaceableKind(kind)) {\n return \"replaceable\";\n }\n if (isEphemeralKind(kind)) {\n return \"ephemeral\";\n }\n if (isAddressableKind(kind)) {\n return \"addressable\";\n }\n return \"unknown\";\n}\n\n/** Kind numbers used by this package. */\nexport const Kind = {\n Metadata: 0,\n TextNote: 1,\n Contacts: 3,\n EventDeletion: 5,\n Repost: 6,\n Reaction: 7,\n Seal: 13,\n PrivateDirectMessage: 14,\n GenericRepost: 16,\n GiftWrap: 1059,\n ZapRequest: 9734,\n Zap: 9735,\n MuteList: 10000,\n PinList: 10001,\n RelayList: 10002,\n BookmarkList: 10003,\n FavoriteRelays: 10012,\n UserEmojiList: 10030,\n DirectMessageRelaysList: 10050,\n BlossomServerList: 10063,\n GiftWrapEphemeral: 21059,\n ClientAuth: 22242,\n NostrConnect: 24133,\n BlobsAuth: 24242,\n HttpAuth: 27235,\n RelaySets: 30002,\n EmojiSet: 30030,\n StarterPack: 39089,\n} as const;\n\n/** Name of a kind constant on {@link Kind}. */\nexport type KindName = keyof typeof Kind;\n/** Union of the numeric kind values on {@link Kind}. */\nexport type KnownKind = (typeof Kind)[KindName];\n"],"mappings":";;AACA,SAAgB,cAAc,MAAuB;CACnD,OAAO,SAAS,KAAK,SAAS,KAAM,QAAQ,KAAK,OAAO,MAAQ,QAAQ,OAAQ,OAAO;AACzF;;AAGA,SAAgB,kBAAkB,MAAuB;CACvD,OAAO,SAAS,KAAK,SAAS,KAAM,QAAQ,OAAS,OAAO;AAC9D;;AAGA,SAAgB,gBAAgB,MAAuB;CACrD,OAAO,QAAQ,OAAS,OAAO;AACjC;;AAGA,SAAgB,kBAAkB,MAAuB;CACvD,OAAO,QAAQ,OAAS,OAAO;AACjC;;AAWA,SAAgB,aAAa,MAAkC;CAC7D,IAAI,cAAc,IAAI,GACpB,OAAO;CAET,IAAI,kBAAkB,IAAI,GACxB,OAAO;CAET,IAAI,gBAAgB,IAAI,GACtB,OAAO;CAET,IAAI,kBAAkB,IAAI,GACxB,OAAO;CAET,OAAO;AACT;;AAGA,MAAa,OAAO;CAClB,UAAU;CACV,UAAU;CACV,UAAU;CACV,eAAe;CACf,QAAQ;CACR,UAAU;CACV,MAAM;CACN,sBAAsB;CACtB,eAAe;CACf,UAAU;CACV,YAAY;CACZ,KAAK;CACL,UAAU;CACV,SAAS;CACT,WAAW;CACX,cAAc;CACd,gBAAgB;CAChB,eAAe;CACf,yBAAyB;CACzB,mBAAmB;CACnB,mBAAmB;CACnB,YAAY;CACZ,cAAc;CACd,WAAW;CACX,UAAU;CACV,WAAW;CACX,UAAU;CACV,aAAa;AACf"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pool-vlw74C4t.mjs","names":[],"sources":["../src/relay/url.ts","../src/relay/fan-in.ts","../src/relay/pool.ts"],"sourcesContent":["import { normalizeURL } from \"../core/util.ts\";\n\n/**\n * {@link normalizeURL}, or the raw string when it is not a valid relay URL (ensureRelay then fails\n * it like a dead relay).\n */\nexport function relayUrlKey(url: string): string {\n try {\n return normalizeURL(url);\n } catch {\n return url;\n }\n}\n\n/** Unique relay keys in first-seen order. */\nexport function uniqueRelayUrls(urls: Iterable<string>): string[] {\n const seen = new Set<string>();\n const unique: string[] = [];\n for (const url of urls) {\n const key = relayUrlKey(url);\n if (seen.has(key)) {\n continue;\n }\n seen.add(key);\n unique.push(key);\n }\n return unique;\n}\n\n/**\n * True for `ws:` / `http:` URLs that are not `.onion`. Local-network `ws://` is still insecure;\n * allow those via a trusted set.\n */\nexport function isInsecureRelayUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n if (parsed.hostname.endsWith(\".onion\")) {\n return false;\n }\n return parsed.protocol === \"ws:\" || parsed.protocol === \"http:\";\n } catch {\n return false;\n }\n}\n","import { abortReason, onAbort, throwIfAborted } from \"../core/abort.ts\";\nimport type { Event } from \"../core/event.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { invokeSafely } from \"../core/report.ts\";\nimport { RelayClosedError } from \"./error.ts\";\nimport type { Pool } from \"./pool.ts\";\nimport type { Relay } from \"./relay.ts\";\nimport type { Closer } from \"./subscription.ts\";\nimport { uniqueRelayUrls } from \"./url.ts\";\n\nexport type RoutedJob = {\n urls: ReadonlyArray<string>;\n filters: ReadonlyArray<Filter>;\n id?: string | undefined;\n};\n\nexport type FanInOptions = {\n /** First receipt only (deduped across jobs). `relayUrl` is the normalized relay URL. */\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n oneose?: (() => void) | undefined;\n onclose?: ((reason: string) => void) | undefined;\n signal?: AbortSignal | undefined;\n /** Armed at fan-in only. Never forwarded to relay.subscribe. */\n eoseTimeoutMs?: number | undefined;\n alreadyHaveEvent?: ((id: string) => boolean) | undefined;\n /** Every receipt from every relay, including skipped duplicates. */\n receivedEvent?: ((id: string, relayUrl: string) => void) | undefined;\n closeOnEose?: boolean | undefined;\n /** Optional. ensureRelay uses Pool.#opts.connectTimeoutMs when omitted. */\n connectTimeoutMs?: number | undefined;\n};\n\n/**\n * Aggregate EOSE/close across routed jobs. One `seen` set. `pending` counts URL list entries\n * (duplicates included). `pendingEose` counts unique `jobIndex:url` keys.\n */\nexport function fanIn(pool: Pool, jobs: ReadonlyArray<RoutedJob>, opts: FanInOptions = {}): Closer {\n const seen = new Set<string>();\n const closers: Closer[] = [];\n let closed = false;\n let eoseFired = false;\n let sawEose = false;\n let eoseTimer: ReturnType<typeof setTimeout> | undefined;\n const eoseDone = new Set<string>();\n const eoseAttempted = new Set<string>();\n let pendingEose = 0;\n let pending = 0;\n\n const fireEose = () => {\n if (closed || eoseFired) {\n return;\n }\n eoseFired = true;\n if (eoseTimer !== undefined) {\n clearTimeout(eoseTimer);\n eoseTimer = undefined;\n }\n invokeSafely(() => opts.oneose?.());\n };\n\n /**\n * Remove one relay from the pending-EOSE set. `eosed` credits a real EOSE; a CLOSED or a connect\n * failure only drops the wait — the aggregate `oneose` fires once every remaining relay ended AND\n * at least one actually EOSE'd.\n */\n const settleEose = (jobIndex: number, url: string, eosed: boolean) => {\n const key = `${jobIndex}:${url}`;\n if (eoseDone.has(key)) {\n return;\n }\n eoseDone.add(key);\n if (eosed) {\n sawEose = true;\n }\n pendingEose -= 1;\n if (pendingEose === 0 && sawEose) {\n fireEose();\n }\n };\n\n const settleClose = () => {\n closed = true;\n disposeAbort();\n if (eoseTimer !== undefined) {\n clearTimeout(eoseTimer);\n eoseTimer = undefined;\n }\n };\n\n const closeAll = (reason?: string) => {\n if (closed) {\n return;\n }\n settleClose();\n for (const c of closers) {\n c.close(reason);\n }\n invokeSafely(() => opts.onclose?.(reason ?? \"closed by client\"));\n };\n\n if (opts.signal?.aborted === true) {\n closed = true;\n invokeSafely(() => opts.onclose?.(\"aborted\"));\n return { close: closeAll };\n }\n\n const disposeAbort = onAbort(opts.signal, () => closeAll(\"aborted\"));\n\n const failUrl = (jobIndex: number, key: string): void => {\n settleEose(jobIndex, key, false);\n pending -= 1;\n if (pending <= 0 && closers.length === 0 && !closed) {\n settleClose();\n invokeSafely(() => opts.onclose?.(\"all relays failed\"));\n }\n };\n\n const attach = (relay: Relay, job: RoutedJob, jobIndex: number): void => {\n if (closed) {\n return;\n }\n const received = opts.receivedEvent;\n const sub = relay.subscribe([...job.filters], {\n id: jobs.length === 1 ? job.id : undefined,\n closeOnEose: opts.closeOnEose,\n alreadyHaveEvent: (id) => {\n let have = seen.has(id);\n invokeSafely(() => {\n have = have || Boolean(opts.alreadyHaveEvent?.(id));\n });\n return have;\n },\n receivedEvent:\n received === undefined ? undefined : (id) => invokeSafely(() => received(id, relay.url)),\n onevent: (event) => {\n seen.add(event.id);\n invokeSafely(() => opts.onevent?.(event, relay.url));\n },\n oneose: () => settleEose(jobIndex, relay.url, true),\n onclose: (reason) => {\n settleEose(jobIndex, relay.url, false);\n pending -= 1;\n if (pending <= 0 && !closed) {\n settleClose();\n invokeSafely(() => opts.onclose?.(reason));\n }\n },\n });\n closers.push(sub);\n };\n\n for (const [jobIndex, job] of jobs.entries()) {\n // Equivalent spellings of one relay URL attach exactly once per job.\n for (const key of uniqueRelayUrls(job.urls)) {\n pending += 1;\n const eoseKey = `${jobIndex}:${key}`;\n if (!eoseAttempted.has(eoseKey)) {\n eoseAttempted.add(eoseKey);\n pendingEose += 1;\n }\n\n const tryAttach = (relay: Relay): void => {\n try {\n attach(relay, job, jobIndex);\n } catch (error) {\n if (error instanceof RelayClosedError) {\n failUrl(jobIndex, key);\n return;\n }\n throw error;\n }\n };\n\n void (async (): Promise<void> => {\n let relay: Relay;\n try {\n relay = await pool.ensureRelay(key, {\n signal: opts.signal,\n timeoutMs: opts.connectTimeoutMs,\n });\n } catch {\n // ensureRelay rejected only — not tryAttach throws (those must not look like connect failure).\n const existing = pool.getRelay(key);\n if (existing !== undefined) {\n tryAttach(existing);\n return;\n }\n failUrl(jobIndex, key);\n return;\n }\n tryAttach(relay);\n })();\n }\n }\n\n if (opts.eoseTimeoutMs !== undefined && pending > 0) {\n eoseTimer = setTimeout(() => {\n eoseTimer = undefined;\n fireEose();\n }, opts.eoseTimeoutMs);\n }\n\n if (pending === 0) {\n queueMicrotask(() => {\n if (closed) {\n return;\n }\n settleClose();\n invokeSafely(() => opts.onclose?.(\"no relays\"));\n });\n }\n\n return { close: closeAll };\n}\n\nexport async function fetchRouted(\n pool: Pool,\n jobs: ReadonlyArray<RoutedJob>,\n opts: {\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n connectTimeoutMs?: number | undefined;\n /** Every event of every relay batch, including cross-relay duplicates. */\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n } = {},\n): Promise<Event[]> {\n throwIfAborted(opts.signal);\n const byId = new Map<string, Event>();\n await Promise.all(\n jobs.flatMap((job) => {\n return uniqueRelayUrls(job.urls).map(async (url) => {\n let batch: ReadonlyArray<Event>;\n let relayUrl: string;\n try {\n const relay = await pool.ensureRelay(url, {\n signal: opts.signal,\n timeoutMs: opts.connectTimeoutMs,\n });\n relayUrl = relay.url;\n const result = await relay.fetch([...job.filters], {\n timeoutMs: opts.timeoutMs,\n signal: opts.signal,\n });\n batch = result.events;\n } catch {\n // An abort rejects the whole call; per-relay failures are skipped.\n if (opts.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return;\n }\n // The whole batch lands before callbacks so a throwing onevent cannot\n // drop events; listener errors are reported, never propagated.\n for (const event of batch) {\n if (!byId.has(event.id)) {\n byId.set(event.id, event);\n }\n }\n for (const event of batch) {\n invokeSafely(() => opts.onevent?.(event, relayUrl));\n }\n });\n }),\n );\n return [...byId.values()];\n}\n","import { abortReason, throwIfAborted } from \"../core/abort.ts\";\nimport { Emitter } from \"../core/emitter.ts\";\nimport { MessageError, toError } from \"../core/error.ts\";\nimport type { Event, EventTemplate } from \"../core/event.ts\";\nimport { canonicalizeFilters } from \"../core/filter.ts\";\nimport type { Filter } from \"../core/filter.ts\";\nimport { assertSubscriptionId } from \"../core/message.ts\";\nimport type { CountResult } from \"../core/message.ts\";\nimport { normalizeURL } from \"../core/util.ts\";\nimport { RelayConnectionError, RelayPublishError, RelaySuspendedError } from \"./error.ts\";\nimport { fanIn, fetchRouted } from \"./fan-in.ts\";\nimport { Relay, RelayStatus } from \"./relay.ts\";\nimport type { RelayFetchEnd, RelayOptions, SubscribeOptions } from \"./relay.ts\";\nimport type { Closer } from \"./subscription.ts\";\nimport { isInsecureRelayUrl, uniqueRelayUrls } from \"./url.ts\";\n\n/** Per-relay tolerance for events that fail id/signature verification. */\nexport type InvalidEventPolicy = {\n /** Invalid events tolerated within `windowMs`; exceeding it suspends the relay. */\n limit: number;\n windowMs: number;\n /** How long a suspended relay stays closed before it may reconnect. */\n cooldownMs: number;\n};\n\n/** Pool-wide options applied to every managed relay. */\nexport type PoolOptions = Omit<RelayOptions, \"authSigner\"> & {\n /**\n * When set, automatically answer NIP-42 AUTH challenges for relays that send them. Return\n * undefined to skip a given relay URL.\n */\n automaticallyAuth?:\n | ((relayURL: string) => ((event: EventTemplate) => Promise<Event>) | undefined)\n | undefined;\n /** When false (default), ensureRelay rejects isInsecureRelayUrl unless trusted. */\n allowInsecure?: boolean | undefined;\n trustedInsecureUrls?: ReadonlyArray<string> | undefined;\n /** Close unused relays. Unset/0 = disabled. */\n idleTimeoutMs?: number | undefined;\n idleCleanupIntervalMs?: number | undefined;\n /**\n * Soft cap on connected non-pinned relays. When `ensureRelay` would create a new non-pinned relay\n * at the cap, the least-recently-used idle one is closed first (idle = no subscriptions and no\n * in-flight requests). If none is idle the connect proceeds anyway — the cap never fails a\n * request.\n */\n maxRelays?: number | undefined;\n /** Normalized URLs never closed by idle cleanup or `maxRelays` eviction. */\n pinnedUrls?: ReadonlyArray<string> | undefined;\n /**\n * When set, a relay whose `limit`-th-plus-one EVENT fails id/signature verification inside a\n * `windowMs` sliding window is disconnected and suspended for `cooldownMs`. During suspension\n * `ensureRelay` rejects with {@link RelaySuspendedError}; once it lifts, live subscriptions resume\n * through the normal reconnect path. Unset = events are dropped without counting, as before.\n */\n invalidEventPolicy?: InvalidEventPolicy | undefined;\n};\n\n/** Typed event payloads for {@link Pool.on}. */\nexport type PoolEventMap = {\n /** Normalized URLs closed by an idle-cleanup pass. */\n idle: ReadonlyArray<string>;\n /**\n * A relay suspended for exceeding `invalidEventPolicy`: `url` is the normalized relay URL and\n * `until` the epoch-ms time the suspension lifts.\n */\n suspend: { readonly url: string; readonly until: number };\n};\n\n/** Per-relay publish outcome: the relay's OK verdict or a transport-level failure. */\nexport type PoolPublishResult =\n | { readonly url: string; readonly status: \"ok\"; readonly message: string }\n | { readonly url: string; readonly status: \"rejected\"; readonly message: string }\n | { readonly url: string; readonly status: \"failed\"; readonly error: Error };\n\n/** Multi-relay subscribe options: callbacks also receive the normalized relay URL. */\nexport type PoolSubscribeOptions = Omit<SubscribeOptions, \"onevent\" | \"receivedEvent\"> & {\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n receivedEvent?: ((id: string, relayUrl: string) => void) | undefined;\n};\n\n/** Per-relay one-shot fetch outcome: collected events plus how its REQ ended. */\nexport type PoolFetchResult = {\n readonly url: string;\n readonly events: ReadonlyArray<Event>;\n readonly end: RelayFetchEnd | { readonly type: \"failed\"; readonly error: Error };\n};\n\n/** Per-relay NIP-45 COUNT result (or a transport-level failure). */\nexport type PoolCountResult =\n | ({ readonly url: string; readonly status: \"ok\" } & CountResult)\n | { readonly url: string; readonly status: \"failed\"; readonly error: Error };\n\nconst countOk = (url: string, payload: CountResult): PoolCountResult => ({\n url,\n status: \"ok\",\n ...payload,\n});\n\nfunction isIdle(relay: Relay): boolean {\n return (\n relay.status !== RelayStatus.Connecting &&\n relay.subscriptionCount === 0 &&\n relay.inFlightCount === 0\n );\n}\n\n/** Multi-relay coordinator: connection reuse, cross-relay event dedup, fan-out publish. */\nexport class Pool {\n readonly #relays = new Map<string, Relay>();\n readonly #relayOptions: Omit<RelayOptions, \"authSigner\">;\n readonly #automaticallyAuth: PoolOptions[\"automaticallyAuth\"];\n readonly #maxRelays: number | undefined;\n readonly #invalidEventPolicy: InvalidEventPolicy | undefined;\n readonly #events = new Emitter<PoolEventMap>();\n readonly #lastActivity = new Map<string, number>();\n readonly #idleTimeoutMs: number;\n #idleTimer: ReturnType<typeof setInterval> | undefined;\n #allowInsecure: boolean;\n #trustedInsecure: Set<string>;\n #pinned: Set<string>;\n /** Per-relay timestamps of verification failures inside the sliding window. */\n readonly #invalidEvents = new Map<string, number[]>();\n /** Normalized URL → epoch ms when its suspension lifts. */\n readonly #suspendedUntil = new Map<string, number>();\n readonly #resumeTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n constructor(opts: PoolOptions = {}) {\n const {\n automaticallyAuth,\n allowInsecure,\n trustedInsecureUrls,\n idleTimeoutMs,\n idleCleanupIntervalMs,\n maxRelays,\n pinnedUrls,\n invalidEventPolicy,\n ...relayOptions\n } = opts;\n this.#relayOptions = relayOptions;\n this.#automaticallyAuth = automaticallyAuth;\n this.#maxRelays = maxRelays;\n this.#invalidEventPolicy = invalidEventPolicy;\n this.#allowInsecure = allowInsecure ?? false;\n this.#trustedInsecure = new Set((trustedInsecureUrls ?? []).map(normalizeURL));\n this.#pinned = new Set((pinnedUrls ?? []).map(normalizeURL));\n this.#idleTimeoutMs = idleTimeoutMs ?? 0;\n if (this.#idleTimeoutMs > 0) {\n this.#idleTimer = setInterval(() => this.cleanIdleRelays(), idleCleanupIntervalMs ?? 30_000);\n }\n }\n\n /**\n * Listen for a pool event. Multiple listeners per type are allowed and fire in registration\n * order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe\n * function.\n */\n on<K extends keyof PoolEventMap>(\n type: K,\n listener: (payload: PoolEventMap[K]) => void,\n ): () => void {\n return this.#events.on(type, listener);\n }\n\n setAllowInsecure(allow: boolean): void {\n this.#allowInsecure = allow;\n }\n\n setTrustedInsecureUrls(urls: ReadonlyArray<string>): void {\n this.#trustedInsecure = new Set(urls.map(normalizeURL));\n }\n\n setPinnedUrls(urls: ReadonlyArray<string>): void {\n this.#pinned = new Set(urls.map(normalizeURL));\n }\n\n /**\n * Drop cached AUTH rejections and re-fire pending challenges on every pooled relay, so a later\n * `setSigner` takes effect without reconnect.\n */\n resetAuth(): void {\n for (const relay of this.#relays.values()) {\n relay.resetAuth();\n }\n }\n\n cleanIdleRelays(): void {\n if (this.#idleTimeoutMs <= 0) {\n return;\n }\n const now = Date.now();\n const idle: string[] = [];\n for (const [url, relay] of this.#relays) {\n if (this.#pinned.has(url) || !isIdle(relay)) {\n continue;\n }\n const last = this.#lastActivity.get(url) ?? 0;\n if (!relay.connected || now - last >= this.#idleTimeoutMs) {\n idle.push(url);\n }\n }\n for (const url of this.#lastActivity.keys()) {\n if (!this.#relays.has(url)) {\n this.#lastActivity.delete(url);\n }\n }\n if (idle.length === 0) {\n return;\n }\n this.closeRelays(idle);\n this.#events.emit(\"idle\", idle);\n }\n\n #touch(url: string): void {\n this.#lastActivity.set(url, Date.now());\n }\n\n /**\n * Count one verification failure for `norm`; on the `limit + 1`-th inside `windowMs` suspend the\n * relay: drop its connection (subscriptions kept), record `until`, and emit `suspend`.\n */\n #noteInvalidEvent(norm: string): void {\n const policy = this.#invalidEventPolicy;\n if (policy === undefined) {\n return;\n }\n const now = Date.now();\n const window = (this.#invalidEvents.get(norm) ?? []).filter((at) => now - at < policy.windowMs);\n window.push(now);\n if (window.length <= policy.limit) {\n this.#invalidEvents.set(norm, window);\n return;\n }\n this.#invalidEvents.delete(norm);\n if (this.#suspendedUntil.has(norm)) {\n return;\n }\n const until = now + policy.cooldownMs;\n this.#suspendedUntil.set(norm, until);\n this.#relays.get(norm)?.disconnect();\n const resume = setTimeout(() => {\n this.#resumeTimers.delete(norm);\n this.#suspendedUntil.delete(norm);\n const relay = this.#relays.get(norm);\n if (relay === undefined || relay.connected || relay.subscriptionCount === 0) {\n return;\n }\n void (async (): Promise<void> => {\n try {\n await relay.connect();\n } catch {\n // With enableReconnect the relay's own backoff retries; otherwise the\n // next ensureRelay attempt reconnects on demand.\n }\n })();\n }, policy.cooldownMs);\n this.#resumeTimers.set(norm, resume);\n this.#events.emit(\"suspend\", { url: norm, until });\n }\n\n #rejectInsecure(url: string, norm: string): void {\n if (this.#allowInsecure) {\n return;\n }\n if (!isInsecureRelayUrl(url)) {\n return;\n }\n if (this.#trustedInsecure.has(norm)) {\n return;\n }\n throw new RelayConnectionError(\"insecure relay connection blocked\", norm);\n }\n\n #stopIdleCleanup(): void {\n if (this.#idleTimer === undefined) {\n return;\n }\n clearInterval(this.#idleTimer);\n this.#idleTimer = undefined;\n }\n\n /**\n * Soft cap: closing the least-recently-used idle non-pinned relay when a new non-pinned relay\n * would push the count past `maxRelays`. Busy relays are never evicted; with none idle the cap is\n * exceeded rather than failing.\n */\n #enforceMaxRelays(incoming: string): void {\n const cap = this.#maxRelays;\n if (cap === undefined || this.#pinned.has(incoming)) {\n return;\n }\n let count = 0;\n let oldestUrl: string | undefined;\n let oldestAt = Number.POSITIVE_INFINITY;\n for (const [url, relay] of this.#relays) {\n if (this.#pinned.has(url)) {\n continue;\n }\n count += 1;\n if (!isIdle(relay)) {\n continue;\n }\n const at = this.#lastActivity.get(url) ?? 0;\n if (at < oldestAt) {\n oldestAt = at;\n oldestUrl = url;\n }\n }\n if (count >= cap && oldestUrl !== undefined) {\n this.closeRelays([oldestUrl]);\n }\n }\n\n async ensureRelay(\n url: string,\n opts?: { signal?: AbortSignal | undefined; timeoutMs?: number | undefined },\n ): Promise<Relay> {\n const norm = normalizeURL(url);\n this.#rejectInsecure(url, norm);\n const suspendedUntil = this.#suspendedUntil.get(norm);\n if (suspendedUntil !== undefined) {\n if (Date.now() < suspendedUntil) {\n throw new RelaySuspendedError(norm, suspendedUntil);\n }\n this.#suspendedUntil.delete(norm);\n }\n let relay = this.#relays.get(norm);\n if (!relay) {\n this.#enforceMaxRelays(norm);\n const signFn = this.#automaticallyAuth?.(norm) ?? undefined;\n const created = new Relay(norm, { ...this.#relayOptions, authSigner: signFn });\n // Only drop from the pool on terminal close (reconnect keeps the entry).\n created.on(\"close\", () => {\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n });\n created.on(\"invalidevent\", () => {\n this.#noteInvalidEvent(norm);\n });\n if (signFn) {\n created.on(\"auth\", () => {\n void (async (): Promise<void> => {\n try {\n await created.auth(signFn);\n } catch {\n // auth failure surfaces on subsequent CLOSED/OK; avoid unhandled rejection\n }\n })();\n });\n }\n this.#relays.set(norm, created);\n relay = created;\n }\n this.#touch(norm);\n if (!relay.connected) {\n try {\n await relay.connect({\n signal: opts?.signal,\n timeoutMs: opts?.timeoutMs ?? this.#relayOptions.connectTimeoutMs,\n });\n } catch (error) {\n // Keep the entry when reconnect is enabled so open subscriptions can recover.\n if (this.#relayOptions.enableReconnect !== true) {\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n throw error;\n }\n }\n return relay;\n }\n\n /** Close every pooled relay and clear all pool state. */\n close(): void {\n this.#stopIdleCleanup();\n for (const timer of this.#resumeTimers.values()) {\n clearTimeout(timer);\n }\n this.#resumeTimers.clear();\n this.#suspendedUntil.clear();\n this.#invalidEvents.clear();\n for (const relay of this.#relays.values()) {\n relay.close();\n }\n this.#relays.clear();\n this.#lastActivity.clear();\n }\n\n /** Close and forget the listed relays; other pooled relays are untouched. */\n closeRelays(urls: ReadonlyArray<string>): void {\n for (const url of urls) {\n const norm = normalizeURL(url);\n const timer = this.#resumeTimers.get(norm);\n if (timer !== undefined) {\n clearTimeout(timer);\n this.#resumeTimers.delete(norm);\n }\n this.#relays.get(norm)?.close();\n this.#relays.delete(norm);\n this.#lastActivity.delete(norm);\n }\n }\n\n /**\n * Subscribe across relays. Deduplicates by event id. Returns a closer; callbacks receive every\n * new event once.\n */\n subscribe(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts: PoolSubscribeOptions = {},\n ): Closer {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n if (opts.id !== undefined) {\n assertSubscriptionId(opts.id);\n }\n const canonical = canonicalizeFilters(filters);\n return fanIn(this, [{ urls: relays, filters: canonical, id: opts.id }], {\n onevent: opts.onevent,\n oneose: opts.oneose,\n onclose: opts.onclose,\n signal: opts.signal,\n eoseTimeoutMs: opts.eoseTimeoutMs,\n alreadyHaveEvent: opts.alreadyHaveEvent,\n receivedEvent: opts.receivedEvent,\n closeOnEose: opts.closeOnEose,\n connectTimeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n }\n\n /**\n * One-shot fetch per relay: each entry reports its own events and end reason (`eose`, `closed`,\n * `timeout`), or `failed` when the connect or REQ itself errored. An abort rejects the whole\n * call. Events are not deduped across relays — use {@link fetch} for that.\n */\n async fetchEach(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: { timeoutMs?: number | undefined; signal?: AbortSignal | undefined },\n ): Promise<PoolFetchResult[]> {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n throwIfAborted(opts?.signal);\n const canonical = canonicalizeFilters(filters);\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolFetchResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n signal: opts?.signal,\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.fetch([...canonical], {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n return { url, events: result.events, end: result.end };\n } catch (error) {\n // An abort rejects the whole call; per-relay failures are reported.\n if (opts?.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return {\n url,\n events: [],\n end: { type: \"failed\", error: toError(error) },\n };\n }\n }),\n );\n }\n\n /** Fetch events until each connected relay EOSE or timeout; dedupe by id. */\n async fetch(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: {\n timeoutMs?: number | undefined;\n signal?: AbortSignal | undefined;\n /** Every event of every relay batch, including cross-relay duplicates. */\n onevent?: ((event: Event, relayUrl: string) => void) | undefined;\n },\n ): Promise<Event[]> {\n if (filters.length === 0) {\n throw new MessageError(\"REQ requires at least one filter\");\n }\n return fetchRouted(this, [{ urls: relays, filters: canonicalizeFilters(filters) }], {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n connectTimeoutMs: this.#relayOptions.connectTimeoutMs,\n onevent: opts?.onevent,\n });\n }\n\n /** Publish to all listed relays; returns per-relay outcomes. */\n async publish(\n relays: ReadonlyArray<string>,\n event: Event,\n opts?: { timeoutMs?: number | undefined },\n ): Promise<PoolPublishResult[]> {\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolPublishResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });\n return result.ok\n ? { url, status: \"ok\", message: result.message }\n : { url, status: \"rejected\", message: result.message };\n } catch (error) {\n return { url, status: \"failed\", error: toError(error) };\n }\n }),\n );\n }\n\n /** First successful publish (Promise.any semantics). */\n async publishAny(\n relays: ReadonlyArray<string>,\n event: Event,\n opts?: { timeoutMs?: number | undefined },\n ): Promise<Extract<PoolPublishResult, { status: \"ok\" }>> {\n return Promise.any(\n uniqueRelayUrls(relays).map(async (url) => {\n const relay = await this.ensureRelay(url, {\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });\n if (!result.ok) {\n throw new RelayPublishError(result.message || \"rejected\", relay.url);\n }\n return { url, status: \"ok\" as const, message: result.message };\n }),\n );\n }\n\n /**\n * NIP-45 COUNT across relays. Per-relay outcomes; failures do not throw. Counts are not summed —\n * each relay reports independently (may overlap).\n */\n async count(\n relays: ReadonlyArray<string>,\n filters: ReadonlyArray<Filter>,\n opts?: { timeoutMs?: number | undefined; signal?: AbortSignal | undefined },\n ): Promise<PoolCountResult[]> {\n throwIfAborted(opts?.signal);\n const canonical = canonicalizeFilters(filters);\n return Promise.all(\n uniqueRelayUrls(relays).map(async (url): Promise<PoolCountResult> => {\n try {\n const relay = await this.ensureRelay(url, {\n signal: opts?.signal,\n timeoutMs: this.#relayOptions.connectTimeoutMs,\n });\n this.#touch(relay.url);\n const payload: CountResult = await relay.count(canonical, {\n timeoutMs: opts?.timeoutMs,\n signal: opts?.signal,\n });\n return countOk(url, payload);\n } catch (error) {\n // An abort rejects the whole call; per-relay failures are reported.\n if (opts?.signal?.aborted === true) {\n throw abortReason(opts.signal);\n }\n return { url, status: \"failed\", error: toError(error) };\n }\n }),\n );\n }\n\n listRelays(): string[] {\n return [...this.#relays.keys()];\n }\n\n /** Lookup a pooled relay, including disconnected reconnecting entries. */\n getRelay(url: string): Relay | undefined {\n try {\n return this.#relays.get(normalizeURL(url));\n } catch {\n return this.#relays.get(url);\n }\n }\n\n /** Currently connected URLs. Unlike listRelays(), excludes reconnecting/disconnected entries. */\n connectedUrls(): string[] {\n const urls: string[] = [];\n for (const [url, relay] of this.#relays) {\n if (relay.connected) {\n urls.push(url);\n }\n }\n return urls;\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"put-w13ci3sC.mjs","names":[],"sources":["../src/storage/deletion.ts","../src/storage/put.ts"],"sourcesContent":["import type { Event } from \"../core/event.ts\";\nimport { Kind } from \"../core/kind.ts\";\nimport { eventAddress, parseEventAddress } from \"../core/tag.ts\";\nimport { isHex32 } from \"../core/util.ts\";\n\nexport type DeletionPlan = {\n /** Stored events validated as same-pubkey (not kind 5) and to be removed now. */\n readonly removeIds: ReadonlyArray<string>;\n /** Referenced ids not in the store; remember deletion pubkey until the event arrives. */\n readonly pendingIds: ReadonlyArray<{ readonly id: string; readonly pubkey: string }>;\n /** Replaceable/addressable coordinates tombstoned up to `until` (deletion created_at). */\n readonly coordinates: ReadonlyArray<{ readonly key: string; readonly until: number }>;\n};\n\n/**\n * NIP-09 application plan for a kind 5 event.\n *\n * - `e` tags: delete only when the target exists, is not itself kind 5, and shares pubkey.\n * - Unknown `e` ids are pending until the event arrives (client MUST validate pubkey).\n * - `a` tags: tombstone that coordinate up to this deletion's `created_at` when pubkey matches.\n * - Deleting a deletion request is a no-op.\n */\nexport function planDeletion(\n deletion: Pick<Event, \"pubkey\" | \"created_at\" | \"tags\">,\n getById: (id: string) => Pick<Event, \"id\" | \"pubkey\" | \"kind\"> | undefined,\n): DeletionPlan {\n const { pubkey } = deletion;\n const removeIds: string[] = [];\n const pendingIds: Array<{ id: string; pubkey: string }> = [];\n const coordinates: Array<{ key: string; until: number }> = [];\n const seenIds = new Set<string>();\n const seenCoords = new Set<string>();\n\n for (const tag of deletion.tags) {\n if (tag[0] === \"e\" && tag[1] !== undefined && isHex32(tag[1].toLowerCase())) {\n const id = tag[1].toLowerCase();\n if (seenIds.has(id)) {\n continue;\n }\n seenIds.add(id);\n const existing = getById(id);\n if (!existing) {\n pendingIds.push({ id, pubkey });\n continue;\n }\n if (existing.kind === Kind.EventDeletion) {\n continue;\n }\n if (existing.pubkey !== pubkey) {\n continue;\n }\n removeIds.push(id);\n continue;\n }\n\n if (tag[0] === \"a\" && tag[1] !== undefined) {\n const coord = parseEventAddress(tag[1]);\n if (!coord || coord.pubkey !== pubkey) {\n continue;\n }\n const key = `${coord.kind}:${coord.pubkey}:${coord.identifier}`;\n if (seenCoords.has(key)) {\n continue;\n }\n seenCoords.add(key);\n coordinates.push({ key, until: deletion.created_at });\n }\n }\n\n return { removeIds, pendingIds, coordinates };\n}\n\n/** In-memory NIP-09 tombstones shared by MemoryEventStore and IndexedDbEventStore. */\nexport class DeletionState {\n readonly ids: Set<string> = new Set<string>();\n readonly pending: Map<string, string> = new Map<string, string>();\n readonly coordinates: Map<string, number> = new Map<string, number>();\n\n clear(): void {\n this.ids.clear();\n this.pending.clear();\n this.coordinates.clear();\n }\n\n covers(event: Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\">): boolean {\n if (this.ids.has(event.id)) {\n return true;\n }\n if (event.kind === Kind.EventDeletion) {\n return false;\n }\n const pendingPk = this.pending.get(event.id);\n if (pendingPk !== undefined && event.pubkey === pendingPk) {\n return true;\n }\n const addr = eventAddress(event);\n if (addr === undefined) {\n return false;\n }\n const until = this.coordinates.get(addr);\n return until !== undefined && event.created_at <= until;\n }\n\n absorb(plan: DeletionPlan): void {\n for (const id of plan.removeIds) {\n this.ids.add(id);\n this.pending.delete(id);\n }\n for (const p of plan.pendingIds) {\n if (!this.ids.has(p.id)) {\n this.pending.set(p.id, p.pubkey);\n }\n }\n for (const c of plan.coordinates) {\n const prev = this.coordinates.get(c.key) ?? Number.NEGATIVE_INFINITY;\n // Re-absorbing a coordinate moves it to the newest end so FIFO\n // tombstone caps evict the oldest marker, not the freshest.\n this.coordinates.delete(c.key);\n this.coordinates.set(c.key, Math.max(prev, c.until));\n }\n }\n}\n\nexport function coordinateRemovals(\n coordinates: ReadonlyArray<{ key: string; until: number }>,\n getCurrent: (key: string) => Pick<Event, \"id\" | \"created_at\"> | undefined,\n): string[] {\n const ids: string[] = [];\n for (const c of coordinates) {\n const current = getCurrent(c.key);\n if (current && current.created_at <= c.until) {\n ids.push(current.id);\n }\n }\n return ids;\n}\n","import type { Event } from \"../core/event.ts\";\nimport { isReplaceableWinner, validateSignedEvent } from \"../core/event.ts\";\nimport { isEphemeralKind, Kind } from \"../core/kind.ts\";\nimport { eventAddress } from \"../core/tag.ts\";\nimport { coordinateRemovals, planDeletion } from \"./deletion.ts\";\nimport type { DeletionPlan, DeletionState } from \"./deletion.ts\";\nimport type { PutResult } from \"./types.ts\";\n\nexport function outboxBoundKey(pubkey: string, kind: number): string {\n return `${pubkey.toLowerCase()}:${kind}`;\n}\n\nexport type PutDecision =\n | {\n readonly action: \"skip\";\n readonly result: \"duplicate\" | \"ephemeral\" | \"rejected\" | \"invalid\";\n readonly event: Event;\n }\n | { readonly action: \"tombstone\"; readonly result: \"duplicate\"; readonly event: Event }\n | {\n readonly action: \"delete\";\n readonly result: \"deleted\";\n readonly event: Event;\n readonly plan: DeletionPlan;\n readonly coordIds: ReadonlyArray<string>;\n }\n | {\n readonly action: \"insert\";\n readonly result: \"accepted\" | \"replaced\";\n readonly event: Event;\n readonly address?: string | undefined;\n readonly replaceId?: string | undefined;\n };\n\nexport type PutLookup = {\n deletion: DeletionState;\n getById: (\n id: string,\n ) => Pick<Event, \"id\" | \"pubkey\" | \"kind\" | \"created_at\" | \"tags\"> | undefined;\n /**\n * Current replaceable winner for an address. `evicted: true` marks an eviction watermark: the\n * winning event body was dropped, but its id/created_at still reject older versions, and a re-put\n * of the watermarked id re-inserts it.\n */\n getReplaceable: (\n address: string,\n ) => (Pick<Event, \"id\" | \"created_at\"> & { evicted?: boolean }) | undefined;\n};\n\nexport function decidePut(raw: Event, lookup: PutLookup): PutDecision {\n // Canonical-input precondition: non-canonical events are never indexed.\n if (!validateSignedEvent(raw)) {\n return { action: \"skip\", result: \"invalid\", event: raw };\n }\n const event = raw;\n if (lookup.deletion.ids.has(event.id) || lookup.getById(event.id)) {\n return { action: \"skip\", result: \"duplicate\", event };\n }\n if (event.kind === Kind.EventDeletion) {\n const plan = planDeletion(event, lookup.getById);\n const coordIds = coordinateRemovals(plan.coordinates, lookup.getReplaceable);\n return { action: \"delete\", result: \"deleted\", event, plan, coordIds };\n }\n if (lookup.deletion.covers(event)) {\n return { action: \"tombstone\", result: \"duplicate\", event };\n }\n if (isEphemeralKind(event.kind)) {\n return { action: \"skip\", result: \"ephemeral\", event };\n }\n const address = eventAddress(event);\n if (address !== undefined) {\n const prev = lookup.getReplaceable(address);\n // A stored incumbent rejects stale versions; an eviction watermark does\n // the same, except a re-put of the watermarked id re-inserts it.\n if (prev && prev.id !== event.id && !isReplaceableWinner(event, prev)) {\n return { action: \"skip\", result: \"rejected\", event };\n }\n if (prev !== undefined && prev.evicted !== true) {\n return { action: \"insert\", result: \"replaced\", event, address, replaceId: prev.id };\n }\n return { action: \"insert\", result: \"accepted\", event, address };\n }\n return { action: \"insert\", result: \"accepted\", event };\n}\n\nexport function applyPutMemory(\n s: {\n deletion: DeletionState;\n indexInsert: (event: Event) => void;\n indexRemove: (id: string) => boolean;\n },\n d: PutDecision,\n): PutResult {\n if (d.action === \"skip\") {\n return d.result;\n }\n if (d.action === \"tombstone\") {\n s.deletion.ids.add(d.event.id);\n s.deletion.pending.delete(d.event.id);\n return \"duplicate\";\n }\n if (d.action === \"delete\") {\n s.deletion.pending.delete(d.event.id);\n s.deletion.absorb(d.plan);\n for (const id of d.plan.removeIds) {\n s.indexRemove(id);\n }\n for (const id of d.coordIds) {\n s.deletion.ids.add(id);\n s.indexRemove(id);\n }\n s.indexInsert(d.event);\n return \"deleted\";\n }\n if (d.replaceId !== undefined) {\n s.indexRemove(d.replaceId);\n }\n s.indexInsert(d.event);\n return d.result;\n}\n"],"mappings":"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"}
|