@qntx/nostr 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/abort-Ba8tDrOI.mjs +38 -0
- package/dist/abort-Ba8tDrOI.mjs.map +1 -0
- package/dist/{builder-BphJa-Y-.d.mts → builder-BWn55Paf.d.mts} +20 -14
- package/dist/{builder-q0VsvnYE.mjs → builder-CyrGKRVP.mjs} +32 -16
- package/dist/builder-CyrGKRVP.mjs.map +1 -0
- package/dist/{client-Bd8B1mYm.mjs → client-5DhgYV7P.mjs} +372 -195
- package/dist/client-5DhgYV7P.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 -9
- package/dist/error-BW7re1UN.d.mts +9 -0
- package/dist/error-B__OMBRY.mjs +14 -0
- package/dist/error-B__OMBRY.mjs.map +1 -0
- package/dist/{error-WEFS-R-4.d.mts → error-CR8Urc84.d.mts} +20 -8
- package/dist/{error-CwBxle6N.mjs → error-DrEIM7FO.mjs} +5 -3
- package/dist/error-DrEIM7FO.mjs.map +1 -0
- package/dist/{event-CzoQEFMF.mjs → event-D8Nfoh4K.mjs} +40 -32
- package/dist/event-D8Nfoh4K.mjs.map +1 -0
- package/dist/{event-B5D2jxt_.d.mts → event-fU3Y01PR.d.mts} +23 -20
- package/dist/{filter-BxwjEkZp.mjs → filter-BQl3GlHt.mjs} +42 -35
- package/dist/filter-BQl3GlHt.mjs.map +1 -0
- package/dist/filter-C0IMByE3.d.mts +37 -0
- package/dist/{gossip-C5RVB1UB.d.mts → gossip-BRQwIM_q.d.mts} +25 -23
- package/dist/{gossip-3xkRRCm_.mjs → gossip-oloDMWEj.mjs} +52 -34
- package/dist/gossip-oloDMWEj.mjs.map +1 -0
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/{http-6vevbBlu.d.mts → http-C4HwIu4B.d.mts} +7 -7
- package/dist/{http-D2rhokZo.mjs → http-CuazNRIP.mjs} +10 -6
- package/dist/http-CuazNRIP.mjs.map +1 -0
- package/dist/index-B-CGNDPS.d.mts +207 -0
- package/dist/index-BIhSADwK.d.mts +339 -0
- package/dist/{index-BYMnhRIC.d.mts → index-Ba7bJ7DD.d.mts} +68 -50
- package/dist/{index-DaRyjo7O.d.mts → index-Boe8FshM.d.mts} +13 -7
- package/dist/index-D-TK_KJG.d.mts +84 -0
- package/dist/index-D1bqFHKB.d.mts +188 -0
- package/dist/index.d.mts +22 -20
- package/dist/index.mjs +19 -18
- package/dist/{key-Dn-aX4x4.mjs → key-Bbtys6o7.mjs} +22 -14
- package/dist/key-Bbtys6o7.mjs.map +1 -0
- package/dist/{key-BiGxGLhg.d.mts → key-BlsYhN1V.d.mts} +9 -6
- package/dist/{kind-BLl4cLOc.mjs → kind-Cxwhhkx8.mjs} +2 -1
- package/dist/kind-Cxwhhkx8.mjs.map +1 -0
- package/dist/{loaders-BNQszDAn.mjs → loaders-o6YXB_Ri.mjs} +329 -305
- package/dist/loaders-o6YXB_Ri.mjs.map +1 -0
- package/dist/loaders.d.mts +3 -3
- package/dist/loaders.mjs +2 -2
- package/dist/memory-C2EVx0_O.mjs +46 -0
- package/dist/memory-C2EVx0_O.mjs.map +1 -0
- package/dist/{memory-BCmhZPek.mjs → memory-index-Do1mz0WF.mjs} +212 -82
- package/dist/memory-index-Do1mz0WF.mjs.map +1 -0
- package/dist/{message-C_Uj-Y-c.mjs → message-DcY6Fi9o.mjs} +38 -24
- package/dist/message-DcY6Fi9o.mjs.map +1 -0
- package/dist/{message-DJC-Jz6T.d.mts → message-iGPIqnn0.d.mts} +15 -8
- package/dist/{nip17-BWq-Kfq5.d.mts → nip17-DNEkz8rM.d.mts} +18 -17
- package/dist/{nip19-D7905oIv.d.mts → nip19-BevCJZsp.d.mts} +23 -3
- package/dist/{nip46-C_DoxiS_.d.mts → nip46-DDV4dC56.d.mts} +13 -12
- package/dist/{nip59-DljW_sYh.d.mts → nip59-zQgdLcEr.d.mts} +28 -25
- package/dist/{nip65-Di5hZRuV.d.mts → nip65-DBfh2smt.d.mts} +8 -3
- package/dist/{nip77-ByxlgoQO.d.mts → nip77-DH4RhS5r.d.mts} +9 -8
- package/dist/nips/blossom.d.mts +37 -37
- package/dist/nips/blossom.mjs +67 -52
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.d.mts +2 -3
- package/dist/nips/nip04.mjs +6 -10
- package/dist/nips/nip04.mjs.map +1 -1
- package/dist/nips/nip05.d.mts +37 -43
- package/dist/nips/nip05.mjs +57 -57
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +18 -19
- package/dist/nips/nip10.mjs +76 -61
- package/dist/nips/nip10.mjs.map +1 -1
- package/dist/nips/nip11.d.mts +9 -8
- package/dist/nips/nip11.mjs +14 -11
- package/dist/nips/nip11.mjs.map +1 -1
- package/dist/nips/nip13.d.mts +9 -9
- package/dist/nips/nip13.mjs +15 -17
- package/dist/nips/nip13.mjs.map +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +17 -12
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +1 -1
- package/dist/nips/nip19.mjs +52 -29
- package/dist/nips/nip19.mjs.map +1 -1
- package/dist/nips/nip21.d.mts +5 -6
- package/dist/nips/nip21.mjs +8 -7
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +7 -8
- package/dist/nips/nip27.mjs +26 -25
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.d.mts +5 -6
- package/dist/nips/nip42.mjs +3 -3
- package/dist/nips/nip42.mjs.map +1 -1
- package/dist/nips/nip44.d.mts +24 -7
- package/dist/nips/nip44.mjs +25 -15
- package/dist/nips/nip44.mjs.map +1 -1
- package/dist/nips/nip46.d.mts +1 -1
- package/dist/nips/nip46.mjs +52 -49
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +8 -7
- package/dist/nips/nip49.mjs +16 -12
- package/dist/nips/nip49.mjs.map +1 -1
- package/dist/nips/nip51.d.mts +25 -27
- package/dist/nips/nip51.mjs +47 -36
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.d.mts +12 -13
- package/dist/nips/nip57.mjs +35 -38
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +14 -18
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +11 -8
- package/dist/nips/nip65.mjs.map +1 -1
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +56 -34
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip96.d.mts +21 -12
- package/dist/nips/nip96.mjs +59 -36
- package/dist/nips/nip96.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +12 -12
- package/dist/nips/nip98.mjs +18 -19
- package/dist/nips/nip98.mjs.map +1 -1
- package/dist/pool-B7p_reAm.d.mts +333 -0
- package/dist/{pool-C_j1OmBB.mjs → pool-C2xOcsun.mjs} +792 -568
- package/dist/pool-C2xOcsun.mjs.map +1 -0
- package/dist/reactive-DDlThDSZ.mjs +324 -0
- package/dist/reactive-DDlThDSZ.mjs.map +1 -0
- package/dist/reactive-DFpqtIf_.d.mts +66 -0
- package/dist/relay.d.mts +5 -4
- package/dist/relay.mjs +2 -2
- package/dist/report-CHld4uWj.mjs +32 -0
- package/dist/report-CHld4uWj.mjs.map +1 -0
- package/dist/{signer-CZ6K6FUc.mjs → signer-CKR9EHce.mjs} +107 -72
- package/dist/signer-CKR9EHce.mjs.map +1 -0
- package/dist/signer.d.mts +3 -3
- package/dist/signer.mjs +3 -2
- package/dist/storage-xXxLGCVb.mjs +1376 -0
- package/dist/storage-xXxLGCVb.mjs.map +1 -0
- package/dist/storage.d.mts +4 -4
- package/dist/storage.mjs +5 -4
- package/dist/store.d.mts +2 -0
- package/dist/store.mjs +2 -0
- package/dist/testing.d.mts +135 -0
- package/dist/testing.mjs +963 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-2pCUprRU.d.mts +47 -0
- package/dist/types-CRQEk0Oi.d.mts +19 -0
- package/dist/util-LfQirgDk.mjs +113 -0
- package/dist/util-LfQirgDk.mjs.map +1 -0
- package/dist/wasm.d.mts +4 -4
- package/dist/wasm.mjs +54 -55
- package/dist/wasm.mjs.map +1 -1
- package/dist/websocket-CmeqvAkp.d.mts +27 -0
- package/package.json +93 -39
- package/dist/builder-q0VsvnYE.mjs.map +0 -1
- package/dist/client-Bd8B1mYm.mjs.map +0 -1
- package/dist/error-B49rbDzT.d.mts +0 -7
- package/dist/error-CwBxle6N.mjs.map +0 -1
- package/dist/error-DZEs5OuZ.mjs +0 -24
- package/dist/error-DZEs5OuZ.mjs.map +0 -1
- package/dist/event-CzoQEFMF.mjs.map +0 -1
- package/dist/filter-BxwjEkZp.mjs.map +0 -1
- package/dist/filter-DUEKP3q1.d.mts +0 -40
- package/dist/gossip-3xkRRCm_.mjs.map +0 -1
- package/dist/http-D2rhokZo.mjs.map +0 -1
- package/dist/index-C0C4e-Gn.d.mts +0 -55
- package/dist/index-DgOugrpF.d.mts +0 -275
- package/dist/index-EwL6fHLo.d.mts +0 -267
- package/dist/index-k2YhCgsh.d.mts +0 -57
- package/dist/key-Dn-aX4x4.mjs.map +0 -1
- package/dist/kind-BLl4cLOc.mjs.map +0 -1
- package/dist/loaders-BNQszDAn.mjs.map +0 -1
- package/dist/memory-BCmhZPek.mjs.map +0 -1
- package/dist/message-C_Uj-Y-c.mjs.map +0 -1
- package/dist/pool-C_j1OmBB.mjs.map +0 -1
- package/dist/pool-DhRi7l-b.d.mts +0 -297
- package/dist/signer-CZ6K6FUc.mjs.map +0 -1
- package/dist/storage-DOy0iLRE.mjs +0 -870
- package/dist/storage-DOy0iLRE.mjs.map +0 -1
- package/dist/types-BzyADqnw.d.mts +0 -19
- package/dist/types-Dn24TPFs.d.mts +0 -43
- package/dist/util-6tfLQ9JF.mjs +0 -73
- package/dist/util-6tfLQ9JF.mjs.map +0 -1
|
@@ -1,21 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { t as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
1
|
+
import { g as NostrError, l as normalizeURL } from "./util-LfQirgDk.mjs";
|
|
2
|
+
import { t as Kind } from "./kind-Cxwhhkx8.mjs";
|
|
3
|
+
import { f as sortedEvents, o as itemCompare } from "./event-D8Nfoh4K.mjs";
|
|
4
|
+
import { t as EventBuilder } from "./builder-CyrGKRVP.mjs";
|
|
5
|
+
import { n as canonicalizeFilters, o as matchFilters, t as canonicalizeFilter } from "./filter-BQl3GlHt.mjs";
|
|
6
|
+
import { t as NoSignerError } from "./error-B__OMBRY.mjs";
|
|
7
|
+
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
8
|
+
import { n as fanIn, r as fetchRouted, t as Pool } from "./pool-C2xOcsun.mjs";
|
|
9
|
+
import { r as throwIfAborted } from "./abort-Ba8tDrOI.mjs";
|
|
8
10
|
import { storageFromItems } from "./nips/nip77.mjs";
|
|
9
11
|
import { isGiftWrapKind, requireNip59Crypto, unwrap } from "./nips/nip59.mjs";
|
|
10
12
|
import { Nip17Error, buildChatMessageRumor, dmRelayListEventBuilder, normalizeRecipients, requireDmRelays, wrapDirectMessage } from "./nips/nip17.mjs";
|
|
11
|
-
import { t as Gossip } from "./gossip-
|
|
12
|
-
import { i as createOutboxFeed, t as createLoaders } from "./loaders-
|
|
13
|
-
import { n as toStorageError } from "./error-
|
|
14
|
-
import { t as
|
|
13
|
+
import { t as Gossip } from "./gossip-oloDMWEj.mjs";
|
|
14
|
+
import { i as createOutboxFeed, t as createLoaders } from "./loaders-o6YXB_Ri.mjs";
|
|
15
|
+
import { n as toStorageError } from "./error-DrEIM7FO.mjs";
|
|
16
|
+
import { t as MemoryIndex } from "./memory-index-Do1mz0WF.mjs";
|
|
17
|
+
import { t as MemoryEventStore } from "./memory-C2EVx0_O.mjs";
|
|
18
|
+
import { t as ReactiveEventStore } from "./reactive-DDlThDSZ.mjs";
|
|
15
19
|
//#region src/client/builder.ts
|
|
16
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Fluent constructor for {@link Client}. The constructor is supplied by {@link Client.builder} so
|
|
22
|
+
* this module never imports the `Client` value, keeping the client/builder dependency
|
|
23
|
+
* one-directional.
|
|
24
|
+
*/
|
|
17
25
|
var ClientBuilder = class {
|
|
18
26
|
#opts = {};
|
|
27
|
+
#newClient;
|
|
28
|
+
constructor(newClient) {
|
|
29
|
+
this.#newClient = newClient;
|
|
30
|
+
}
|
|
19
31
|
signer(signer) {
|
|
20
32
|
this.#opts = {
|
|
21
33
|
...this.#opts,
|
|
@@ -72,6 +84,62 @@ var ClientBuilder = class {
|
|
|
72
84
|
};
|
|
73
85
|
return this;
|
|
74
86
|
}
|
|
87
|
+
/** Allow `ws://` relays. Default false. */
|
|
88
|
+
allowInsecure(allow) {
|
|
89
|
+
this.#opts = {
|
|
90
|
+
...this.#opts,
|
|
91
|
+
allowInsecure: allow
|
|
92
|
+
};
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
/** `ws://` URLs allowed despite `allowInsecure` being off. */
|
|
96
|
+
trustedInsecureUrls(urls) {
|
|
97
|
+
this.#opts = {
|
|
98
|
+
...this.#opts,
|
|
99
|
+
trustedInsecureUrls: urls
|
|
100
|
+
};
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
/** Close idle relays after this many ms. Unset = disabled. */
|
|
104
|
+
idleTimeoutMs(ms) {
|
|
105
|
+
this.#opts = {
|
|
106
|
+
...this.#opts,
|
|
107
|
+
idleTimeoutMs: ms
|
|
108
|
+
};
|
|
109
|
+
return this;
|
|
110
|
+
}
|
|
111
|
+
/** Soft cap on connected non-pinned relays. */
|
|
112
|
+
maxRelays(n) {
|
|
113
|
+
this.#opts = {
|
|
114
|
+
...this.#opts,
|
|
115
|
+
maxRelays: n
|
|
116
|
+
};
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
/** Relays never closed by idle cleanup or `maxRelays` eviction. */
|
|
120
|
+
pinnedUrls(urls) {
|
|
121
|
+
this.#opts = {
|
|
122
|
+
...this.#opts,
|
|
123
|
+
pinnedUrls: urls
|
|
124
|
+
};
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
/** Forwarded to the pool: suspend a relay delivering too many invalid events. */
|
|
128
|
+
invalidEventPolicy(policy) {
|
|
129
|
+
this.#opts = {
|
|
130
|
+
...this.#opts,
|
|
131
|
+
invalidEventPolicy: policy
|
|
132
|
+
};
|
|
133
|
+
return this;
|
|
134
|
+
}
|
|
135
|
+
/** Forwarded to the pool: fired once when a relay is suspended, with the lift time. */
|
|
136
|
+
onRelaySuspended(cb) {
|
|
137
|
+
this.#opts = {
|
|
138
|
+
...this.#opts,
|
|
139
|
+
onRelaySuspended: cb
|
|
140
|
+
};
|
|
141
|
+
return this;
|
|
142
|
+
}
|
|
75
143
|
enableReconnect(enabled) {
|
|
76
144
|
this.#opts = {
|
|
77
145
|
...this.#opts,
|
|
@@ -101,8 +169,8 @@ var ClientBuilder = class {
|
|
|
101
169
|
return this;
|
|
102
170
|
}
|
|
103
171
|
/**
|
|
104
|
-
* Local event store. Defaults to {@link MemoryEventStore}.
|
|
105
|
-
*
|
|
172
|
+
* Local event store. Defaults to {@link MemoryEventStore}. Browser apps that want persistence must
|
|
173
|
+
* pass {@link IndexedDbEventStore} and `await open()`.
|
|
106
174
|
*/
|
|
107
175
|
storage(store) {
|
|
108
176
|
this.#opts = {
|
|
@@ -111,6 +179,17 @@ var ClientBuilder = class {
|
|
|
111
179
|
};
|
|
112
180
|
return this;
|
|
113
181
|
}
|
|
182
|
+
/**
|
|
183
|
+
* Synchronous reactive index mirroring ingested events. Defaults to a new
|
|
184
|
+
* {@link ReactiveEventStore}.
|
|
185
|
+
*/
|
|
186
|
+
index(index) {
|
|
187
|
+
this.#opts = {
|
|
188
|
+
...this.#opts,
|
|
189
|
+
index
|
|
190
|
+
};
|
|
191
|
+
return this;
|
|
192
|
+
}
|
|
114
193
|
/** When false, Client will not auto-write events to storage. Default true. */
|
|
115
194
|
persistEvents(enabled) {
|
|
116
195
|
this.#opts = {
|
|
@@ -134,17 +213,18 @@ var ClientBuilder = class {
|
|
|
134
213
|
return this;
|
|
135
214
|
}
|
|
136
215
|
build() {
|
|
137
|
-
return
|
|
216
|
+
return this.#newClient(this.#opts);
|
|
138
217
|
}
|
|
139
218
|
};
|
|
140
219
|
//#endregion
|
|
141
220
|
//#region src/client/dm.ts
|
|
142
221
|
function giftWrapRelays(gossip, event) {
|
|
143
222
|
const targets = [];
|
|
144
|
-
for (const tag of event.tags) if (tag[0] === "p" && tag[1]) targets.push(tag[1].toLowerCase());
|
|
223
|
+
for (const tag of event.tags) if (tag[0] === "p" && tag[1] !== void 0 && tag[1] !== "") targets.push(tag[1].toLowerCase());
|
|
145
224
|
if (targets.length === 0) throw new Nip17Error("gift wrap has no p-tag recipient");
|
|
146
|
-
|
|
147
|
-
|
|
225
|
+
const [target] = targets;
|
|
226
|
+
if (targets.length !== 1 || target === void 0) throw new Nip17Error("gift wrap must have exactly one p-tag recipient");
|
|
227
|
+
const relays = gossip.dmRelays(target);
|
|
148
228
|
if (relays.length === 0) throw new Nip17Error("no kind 10050 in gossip; use sendPrivateMessage or pass relays");
|
|
149
229
|
return relays;
|
|
150
230
|
}
|
|
@@ -174,7 +254,7 @@ async function sendPrivateMessage(deps, recipients, content, opts) {
|
|
|
174
254
|
wraps: await Promise.all(wraps.map(async ({ recipient, wrap }) => {
|
|
175
255
|
const relays = requireDmRelays(recipient, deps.gossip.dmRelays(recipient));
|
|
176
256
|
const results = await deps.pool.publish(relays, wrap, { timeoutMs: opts?.timeoutMs });
|
|
177
|
-
if (results.some((r) => r.result?.ok) && deps.wantObserve(opts?.observe)) deps.
|
|
257
|
+
if (results.some((r) => r.result?.ok === true) && deps.wantObserve(opts?.observe)) deps.ingest(wrap);
|
|
178
258
|
return {
|
|
179
259
|
recipient,
|
|
180
260
|
wrap,
|
|
@@ -191,6 +271,7 @@ async function fetchPrivateMessages(deps, opts) {
|
|
|
191
271
|
await deps.hydrateGossip([self]);
|
|
192
272
|
deps.throwIfAborted(opts?.signal);
|
|
193
273
|
const relays = requireDmRelays(self, deps.gossip.dmRelays(self));
|
|
274
|
+
const urls = /* @__PURE__ */ new Map();
|
|
194
275
|
const events = await deps.pool.fetch(relays, [{
|
|
195
276
|
kinds: [Kind.GiftWrap],
|
|
196
277
|
"#p": [self],
|
|
@@ -198,21 +279,31 @@ async function fetchPrivateMessages(deps, opts) {
|
|
|
198
279
|
until: opts?.until
|
|
199
280
|
}], {
|
|
200
281
|
timeoutMs: opts?.timeoutMs,
|
|
201
|
-
signal: opts?.signal
|
|
282
|
+
signal: opts?.signal,
|
|
283
|
+
onevent: (event, relayUrl) => {
|
|
284
|
+
let set = urls.get(event.id);
|
|
285
|
+
if (!set) urls.set(event.id, set = /* @__PURE__ */ new Set());
|
|
286
|
+
set.add(relayUrl);
|
|
287
|
+
}
|
|
202
288
|
});
|
|
203
289
|
const byRumor = /* @__PURE__ */ new Map();
|
|
204
290
|
for (const wrap of events) try {
|
|
205
291
|
const rumor = await unwrap(crypto, wrap);
|
|
206
|
-
|
|
292
|
+
const wrapUrls = urls.get(wrap.id);
|
|
293
|
+
const firstUrl = wrapUrls?.values().next().value;
|
|
294
|
+
if (deps.wantObserve(opts?.observe)) {
|
|
295
|
+
deps.ingest(wrap, firstUrl);
|
|
296
|
+
if (wrapUrls) {
|
|
297
|
+
for (const url of wrapUrls) if (url !== firstUrl) deps.markSeen(wrap.id, url);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
207
300
|
byRumor.set(rumor.id, {
|
|
208
301
|
wrap,
|
|
209
|
-
rumor
|
|
302
|
+
rumor,
|
|
303
|
+
...firstUrl === void 0 ? {} : { relayUrl: firstUrl }
|
|
210
304
|
});
|
|
211
305
|
} catch {}
|
|
212
|
-
return [...byRumor.values()].sort((a, b) =>
|
|
213
|
-
if (a.rumor.created_at !== b.rumor.created_at) return a.rumor.created_at - b.rumor.created_at;
|
|
214
|
-
return a.rumor.id.localeCompare(b.rumor.id);
|
|
215
|
-
});
|
|
306
|
+
return [...byRumor.values()].sort((a, b) => itemCompare(a.rumor, b.rumor));
|
|
216
307
|
}
|
|
217
308
|
async function subscribePrivateMessages(deps, opts) {
|
|
218
309
|
deps.assertAlive();
|
|
@@ -223,12 +314,21 @@ async function subscribePrivateMessages(deps, opts) {
|
|
|
223
314
|
deps.throwIfAborted(opts?.signal);
|
|
224
315
|
const relays = requireDmRelays(self, deps.gossip.dmRelays(self));
|
|
225
316
|
const seen = /* @__PURE__ */ new Set();
|
|
317
|
+
const processed = /* @__PURE__ */ new Set();
|
|
318
|
+
const pendingUrls = /* @__PURE__ */ new Map();
|
|
319
|
+
const flushSeen = (wrapId) => {
|
|
320
|
+
processed.add(wrapId);
|
|
321
|
+
const extra = pendingUrls.get(wrapId);
|
|
322
|
+
if (extra === void 0) return;
|
|
323
|
+
pendingUrls.delete(wrapId);
|
|
324
|
+
for (const url of extra) deps.markSeen(wrapId, url);
|
|
325
|
+
};
|
|
226
326
|
let tail = Promise.resolve();
|
|
227
327
|
let closed = false;
|
|
228
328
|
const markClosed = () => {
|
|
229
329
|
closed = true;
|
|
230
330
|
};
|
|
231
|
-
if (opts?.signal?.aborted) markClosed();
|
|
331
|
+
if (opts?.signal?.aborted === true) markClosed();
|
|
232
332
|
else opts?.signal?.addEventListener("abort", markClosed, { once: true });
|
|
233
333
|
const inner = deps.pool.subscribe(relays, [{
|
|
234
334
|
kinds: [Kind.GiftWrap, Kind.GiftWrapEphemeral],
|
|
@@ -242,22 +342,45 @@ async function subscribePrivateMessages(deps, opts) {
|
|
|
242
342
|
opts?.onclose?.(reason);
|
|
243
343
|
},
|
|
244
344
|
eoseTimeoutMs: opts?.eoseTimeoutMs,
|
|
245
|
-
|
|
345
|
+
receivedEvent: (id, relayUrl) => {
|
|
246
346
|
if (closed) return;
|
|
247
|
-
|
|
248
|
-
|
|
347
|
+
if (processed.has(id)) {
|
|
348
|
+
deps.markSeen(id, relayUrl);
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
const list = pendingUrls.get(id);
|
|
352
|
+
if (list === void 0) pendingUrls.set(id, [relayUrl]);
|
|
353
|
+
else if (!list.includes(relayUrl)) list.push(relayUrl);
|
|
354
|
+
},
|
|
355
|
+
onevent: (wrap, relayUrl) => {
|
|
356
|
+
if (closed) return;
|
|
357
|
+
const prev = tail;
|
|
358
|
+
let release;
|
|
359
|
+
tail = new Promise((resolve) => {
|
|
360
|
+
release = resolve;
|
|
361
|
+
});
|
|
362
|
+
(async () => {
|
|
363
|
+
await prev;
|
|
249
364
|
try {
|
|
365
|
+
if (closed) return;
|
|
250
366
|
const rumor = await unwrap(crypto, wrap);
|
|
251
367
|
if (closed) return;
|
|
252
|
-
if (seen.has(rumor.id))
|
|
368
|
+
if (seen.has(rumor.id)) {
|
|
369
|
+
flushSeen(wrap.id);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
253
372
|
seen.add(rumor.id);
|
|
254
|
-
if (deps.wantObserve(opts?.observe)) deps.
|
|
373
|
+
if (deps.wantObserve(opts?.observe)) deps.ingest(wrap, relayUrl);
|
|
374
|
+
flushSeen(wrap.id);
|
|
255
375
|
opts?.onevent?.({
|
|
256
376
|
wrap,
|
|
257
|
-
rumor
|
|
377
|
+
rumor,
|
|
378
|
+
relayUrl
|
|
258
379
|
});
|
|
259
|
-
} catch {}
|
|
260
|
-
|
|
380
|
+
} catch {} finally {
|
|
381
|
+
release?.();
|
|
382
|
+
}
|
|
383
|
+
})();
|
|
261
384
|
}
|
|
262
385
|
});
|
|
263
386
|
return { close: (reason) => {
|
|
@@ -266,6 +389,18 @@ async function subscribePrivateMessages(deps, opts) {
|
|
|
266
389
|
} };
|
|
267
390
|
}
|
|
268
391
|
//#endregion
|
|
392
|
+
//#region src/client/types.ts
|
|
393
|
+
/** Direction of a {@link Client.sync} run: upload, download, or both. */
|
|
394
|
+
const SyncDirection = {
|
|
395
|
+
Up: "up",
|
|
396
|
+
Down: "down",
|
|
397
|
+
Both: "both"
|
|
398
|
+
};
|
|
399
|
+
/** Client lifecycle, configuration, or abort failure (not cryptographic). */
|
|
400
|
+
var ClientError = class extends NostrError {
|
|
401
|
+
name = "ClientError";
|
|
402
|
+
};
|
|
403
|
+
//#endregion
|
|
269
404
|
//#region src/client/gossip-io.ts
|
|
270
405
|
/** Remainder is one job on defaults; throw before any REQ when defaults are empty. */
|
|
271
406
|
function jobsForFilters(gossip, filters, defaultRelays) {
|
|
@@ -277,17 +412,21 @@ function jobsForFilters(gossip, filters, defaultRelays) {
|
|
|
277
412
|
urls: [url],
|
|
278
413
|
filters: [sub]
|
|
279
414
|
});
|
|
280
|
-
if (r.remainder)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
415
|
+
if (r.remainder !== void 0) {
|
|
416
|
+
if (defaults === void 0) throw new ClientError("unrouted remainder without default relays");
|
|
417
|
+
jobs.push({
|
|
418
|
+
urls: defaults,
|
|
419
|
+
filters: [r.remainder]
|
|
420
|
+
});
|
|
421
|
+
}
|
|
284
422
|
}
|
|
285
423
|
return jobs;
|
|
286
424
|
}
|
|
287
|
-
function fetchGossip(pool, gossip, filters, defaultRelays, opts) {
|
|
425
|
+
async function fetchGossip(pool, gossip, filters, defaultRelays, opts) {
|
|
288
426
|
return fetchRouted(pool, jobsForFilters(gossip, filters, defaultRelays), {
|
|
289
427
|
timeoutMs: opts?.timeoutMs,
|
|
290
|
-
signal: opts?.signal
|
|
428
|
+
signal: opts?.signal,
|
|
429
|
+
onevent: opts?.onevent
|
|
291
430
|
});
|
|
292
431
|
}
|
|
293
432
|
function subscribeGossip(pool, gossip, filters, defaultRelays, opts) {
|
|
@@ -299,15 +438,6 @@ function subscribeGossip(pool, gossip, filters, defaultRelays, opts) {
|
|
|
299
438
|
return fanIn(pool, jobs, opts);
|
|
300
439
|
}
|
|
301
440
|
//#endregion
|
|
302
|
-
//#region src/client/types.ts
|
|
303
|
-
const SyncDirection = {
|
|
304
|
-
Up: "up",
|
|
305
|
-
Down: "down",
|
|
306
|
-
Both: "both"
|
|
307
|
-
};
|
|
308
|
-
/** Client lifecycle, configuration, or abort failure (not cryptographic). */
|
|
309
|
-
var ClientError = class extends NostrError {};
|
|
310
|
-
//#endregion
|
|
311
441
|
//#region src/client/sync.ts
|
|
312
442
|
const SYNC_ID_BATCH = 100;
|
|
313
443
|
const SYNC_UPLOAD_CONCURRENCY = 8;
|
|
@@ -343,19 +473,18 @@ function mergeSyncSummary(into, other) {
|
|
|
343
473
|
};
|
|
344
474
|
}
|
|
345
475
|
/**
|
|
346
|
-
* NIP-77 sync against one relay: reconcile, then optionally upload
|
|
347
|
-
*
|
|
348
|
-
* `
|
|
349
|
-
* `persistEvents: false` skips putMany, still ingestMeta when observe is on.
|
|
476
|
+
* NIP-77 sync against one relay: reconcile, then optionally upload local-only events and/or
|
|
477
|
+
* download remote-only events. `observe: false` skips putMany and ingest; received ids are still
|
|
478
|
+
* listed. `persistEvents: false` skips putMany, still ingests when observe is on.
|
|
350
479
|
*/
|
|
351
480
|
async function syncToRelay(deps, url, filter, opts) {
|
|
352
481
|
deps.assertAlive();
|
|
353
482
|
deps.throwIfAborted(opts?.signal);
|
|
354
483
|
const direction = opts?.direction ?? SyncDirection.Down;
|
|
355
|
-
|
|
356
|
-
const items = await deps.storage.negentropyItems(
|
|
484
|
+
const canon = canonicalizeFilter(filter);
|
|
485
|
+
const items = await deps.storage.negentropyItems(canon);
|
|
357
486
|
const storage = storageFromItems(items);
|
|
358
|
-
const { have, need } = await (await deps.pool.ensureRelay(url, { signal: opts?.signal })).negReconcile(
|
|
487
|
+
const { have, need } = await (await deps.pool.ensureRelay(url, { signal: opts?.signal })).negReconcile(canon, storage, {
|
|
359
488
|
timeoutMs: opts?.timeoutMs,
|
|
360
489
|
signal: opts?.signal
|
|
361
490
|
});
|
|
@@ -367,24 +496,22 @@ async function syncToRelay(deps, url, filter, opts) {
|
|
|
367
496
|
sendFailures: {},
|
|
368
497
|
persistFailures: {}
|
|
369
498
|
};
|
|
370
|
-
if (opts?.dryRun) return summary;
|
|
371
|
-
if (direction === SyncDirection.Up || direction === SyncDirection.Both) {
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
}));
|
|
387
|
-
}
|
|
499
|
+
if (opts?.dryRun === true) return summary;
|
|
500
|
+
if ((direction === SyncDirection.Up || direction === SyncDirection.Both) && have.length > 0) {
|
|
501
|
+
const found = await deps.storage.query([{ ids: have }]);
|
|
502
|
+
const foundById = new Map(found.map((event) => [event.id, event]));
|
|
503
|
+
for (const id of have) if (!foundById.has(id)) summary.sendFailures[id] = "event not found in local store";
|
|
504
|
+
for (let i = 0; i < found.length; i += SYNC_UPLOAD_CONCURRENCY) {
|
|
505
|
+
const chunk = found.slice(i, i + SYNC_UPLOAD_CONCURRENCY);
|
|
506
|
+
await Promise.all(chunk.map(async (event) => {
|
|
507
|
+
try {
|
|
508
|
+
const results = await deps.pool.publish([url], event, { timeoutMs: opts?.timeoutMs });
|
|
509
|
+
if (results.some((r) => r.result?.ok === true)) summary.sent.push(event.id);
|
|
510
|
+
else summary.sendFailures[event.id] = results[0]?.error ?? results[0]?.result?.message ?? "publish failed";
|
|
511
|
+
} catch (error) {
|
|
512
|
+
summary.sendFailures[event.id] = error instanceof Error ? error.message : String(error);
|
|
513
|
+
}
|
|
514
|
+
}));
|
|
388
515
|
}
|
|
389
516
|
}
|
|
390
517
|
if ((direction === SyncDirection.Down || direction === SyncDirection.Both) && need.length > 0) {
|
|
@@ -392,17 +519,28 @@ async function syncToRelay(deps, url, filter, opts) {
|
|
|
392
519
|
for (let i = 0; i < need.length; i += SYNC_ID_BATCH) {
|
|
393
520
|
const batch = need.slice(i, i + SYNC_ID_BATCH);
|
|
394
521
|
deps.throwIfAborted(opts?.signal);
|
|
522
|
+
const urlsById = /* @__PURE__ */ new Map();
|
|
395
523
|
const events = await deps.pool.fetch([url], [{ ids: batch }], {
|
|
396
524
|
timeoutMs: opts?.timeoutMs,
|
|
397
|
-
signal: opts?.signal
|
|
525
|
+
signal: opts?.signal,
|
|
526
|
+
onevent: shouldObserve ? (event, relayUrl) => {
|
|
527
|
+
const urls = urlsById.get(event.id);
|
|
528
|
+
if (urls === void 0) urlsById.set(event.id, [relayUrl]);
|
|
529
|
+
else if (!urls.includes(relayUrl)) urls.push(relayUrl);
|
|
530
|
+
} : void 0
|
|
398
531
|
});
|
|
399
532
|
if (!shouldObserve) {
|
|
400
533
|
for (const event of events) summary.received.push(event.id);
|
|
401
534
|
continue;
|
|
402
535
|
}
|
|
536
|
+
const ingestAll = (event) => {
|
|
537
|
+
const urls = urlsById.get(event.id) ?? [];
|
|
538
|
+
deps.ingest(event, urls[0], { persist: false });
|
|
539
|
+
for (const url of urls.slice(1)) deps.markSeen(event.id, url);
|
|
540
|
+
};
|
|
403
541
|
if (!deps.persistEvents) {
|
|
404
542
|
for (const event of events) {
|
|
405
|
-
|
|
543
|
+
ingestAll(event);
|
|
406
544
|
summary.received.push(event.id);
|
|
407
545
|
}
|
|
408
546
|
continue;
|
|
@@ -410,15 +548,14 @@ async function syncToRelay(deps, url, filter, opts) {
|
|
|
410
548
|
let results;
|
|
411
549
|
try {
|
|
412
550
|
results = await deps.storage.putMany(events);
|
|
413
|
-
} catch (
|
|
414
|
-
const message =
|
|
551
|
+
} catch (error) {
|
|
552
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
415
553
|
for (const event of events) summary.persistFailures[event.id] = message;
|
|
416
554
|
break;
|
|
417
555
|
}
|
|
418
|
-
for (
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
deps.ingestMeta(event);
|
|
556
|
+
for (const [j, event] of events.entries()) {
|
|
557
|
+
if (results[j] === "rejected" || results[j] === "invalid") continue;
|
|
558
|
+
ingestAll(event);
|
|
422
559
|
summary.received.push(event.id);
|
|
423
560
|
}
|
|
424
561
|
}
|
|
@@ -426,14 +563,14 @@ async function syncToRelay(deps, url, filter, opts) {
|
|
|
426
563
|
return summary;
|
|
427
564
|
}
|
|
428
565
|
/**
|
|
429
|
-
* NIP-77 sync against the given relays (or Client default relays).
|
|
430
|
-
*
|
|
431
|
-
*
|
|
566
|
+
* NIP-77 sync against the given relays (or Client default relays). Independent sessions run in
|
|
567
|
+
* parallel. Fulfilled summaries are merged; if every relay rejects, throws the first rejection in
|
|
568
|
+
* URL order.
|
|
432
569
|
*/
|
|
433
570
|
async function sync(deps, filter, opts) {
|
|
434
571
|
deps.assertAlive();
|
|
435
572
|
const urls = deps.defaultRelays(opts?.relays ? [...opts.relays] : void 0);
|
|
436
|
-
const results = await Promise.allSettled(urls.map((url) => syncToRelay(deps, url, filter, opts)));
|
|
573
|
+
const results = await Promise.allSettled(urls.map(async (url) => syncToRelay(deps, url, filter, opts)));
|
|
437
574
|
let merged = emptySummary();
|
|
438
575
|
let fulfilled = 0;
|
|
439
576
|
let firstRejection;
|
|
@@ -446,14 +583,13 @@ async function sync(deps, filter, opts) {
|
|
|
446
583
|
}
|
|
447
584
|
//#endregion
|
|
448
585
|
//#region src/client/client.ts
|
|
449
|
-
/**
|
|
450
|
-
|
|
451
|
-
*/
|
|
452
|
-
var Client = class {
|
|
586
|
+
/** Layer-5 facade: signer + default relays + pool + loaders + gossip + event store. */
|
|
587
|
+
var Client = class Client {
|
|
453
588
|
pool;
|
|
454
589
|
loaders;
|
|
455
590
|
gossip;
|
|
456
591
|
storage;
|
|
592
|
+
index;
|
|
457
593
|
onstorageerror;
|
|
458
594
|
#signer;
|
|
459
595
|
#relays;
|
|
@@ -463,12 +599,16 @@ var Client = class {
|
|
|
463
599
|
#flushing;
|
|
464
600
|
constructor(opts = {}) {
|
|
465
601
|
this.#signer = opts.signer;
|
|
466
|
-
this.#relays = [
|
|
602
|
+
this.#relays = [];
|
|
603
|
+
for (const raw of opts.relays ?? []) {
|
|
604
|
+
const url = normalizeURL(raw);
|
|
605
|
+
if (!this.#relays.includes(url)) this.#relays.push(url);
|
|
606
|
+
}
|
|
467
607
|
this.gossip = opts.gossip ?? new Gossip();
|
|
468
608
|
this.storage = opts.storage ?? new MemoryEventStore();
|
|
609
|
+
this.index = opts.index ?? new ReactiveEventStore();
|
|
469
610
|
this.#persistEvents = opts.persistEvents ?? true;
|
|
470
|
-
this.onstorageerror = opts.onstorageerror
|
|
471
|
-
const autoAuth = opts.automaticAuth ?? Boolean(opts.signer);
|
|
611
|
+
this.onstorageerror = opts.onstorageerror;
|
|
472
612
|
this.pool = new Pool({
|
|
473
613
|
websocketImplementation: opts.websocketImplementation,
|
|
474
614
|
verifyEvent: opts.verifyEvent,
|
|
@@ -478,25 +618,32 @@ var Client = class {
|
|
|
478
618
|
connectTimeoutMs: opts.connectTimeoutMs,
|
|
479
619
|
publishTimeoutMs: opts.publishTimeoutMs,
|
|
480
620
|
enableReconnect: opts.enableReconnect ?? true,
|
|
481
|
-
|
|
621
|
+
allowInsecure: opts.allowInsecure,
|
|
622
|
+
trustedInsecureUrls: opts.trustedInsecureUrls,
|
|
623
|
+
idleTimeoutMs: opts.idleTimeoutMs,
|
|
624
|
+
maxRelays: opts.maxRelays,
|
|
625
|
+
pinnedUrls: opts.pinnedUrls,
|
|
626
|
+
invalidEventPolicy: opts.invalidEventPolicy,
|
|
627
|
+
onRelaySuspended: opts.onRelaySuspended,
|
|
628
|
+
automaticallyAuth: opts.automaticAuth ?? true ? () => async (template) => {
|
|
482
629
|
const signer = this.#signer;
|
|
483
|
-
if (!signer)
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
});
|
|
490
|
-
};
|
|
630
|
+
if (!signer) throw new NoSignerError("no signer configured for AUTH");
|
|
631
|
+
const pk = await signer.getPublicKey();
|
|
632
|
+
return signer.signEvent({
|
|
633
|
+
...template,
|
|
634
|
+
pubkey: pk
|
|
635
|
+
});
|
|
491
636
|
} : void 0
|
|
492
637
|
});
|
|
493
638
|
this.loaders = createLoaders({
|
|
494
639
|
pool: this.pool,
|
|
495
|
-
relays: this.#relays
|
|
640
|
+
relays: this.#relays,
|
|
641
|
+
index: this.index,
|
|
642
|
+
ingest: (event, relayUrl) => this.#ingest(event, relayUrl)
|
|
496
643
|
});
|
|
497
644
|
}
|
|
498
645
|
static builder() {
|
|
499
|
-
return new ClientBuilder();
|
|
646
|
+
return new ClientBuilder((opts) => new Client(opts));
|
|
500
647
|
}
|
|
501
648
|
get signer() {
|
|
502
649
|
return this.#signer;
|
|
@@ -509,28 +656,30 @@ var Client = class {
|
|
|
509
656
|
}
|
|
510
657
|
setSigner(signer) {
|
|
511
658
|
this.#signer = signer;
|
|
659
|
+
this.pool.resetAuth();
|
|
512
660
|
}
|
|
513
661
|
addRelay(url) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
this.
|
|
662
|
+
const normalized = normalizeURL(url);
|
|
663
|
+
if (!this.#relays.includes(normalized)) {
|
|
664
|
+
this.#relays.push(normalized);
|
|
665
|
+
this.loaders.addRelay(normalized);
|
|
517
666
|
}
|
|
518
667
|
}
|
|
519
668
|
removeRelay(url) {
|
|
520
|
-
|
|
521
|
-
this.
|
|
522
|
-
this.
|
|
669
|
+
const normalized = normalizeURL(url);
|
|
670
|
+
this.#relays = this.#relays.filter((r) => r !== normalized);
|
|
671
|
+
this.loaders.removeRelay(normalized);
|
|
672
|
+
this.pool.close([normalized]);
|
|
523
673
|
}
|
|
524
674
|
/** Connect all configured relays (best-effort; failures are ignored). */
|
|
525
675
|
async connect(opts) {
|
|
526
676
|
this.#assertAlive();
|
|
527
|
-
await Promise.allSettled(this.#relays.map((url) => this.pool.ensureRelay(url, { signal: opts?.signal })));
|
|
677
|
+
await Promise.allSettled(this.#relays.map(async (url) => this.pool.ensureRelay(url, { signal: opts?.signal })));
|
|
528
678
|
}
|
|
529
679
|
async shutdown() {
|
|
530
680
|
this.#shutdown = true;
|
|
531
|
-
while (this.#flushing) await this.#flushing;
|
|
681
|
+
while (this.#flushing !== void 0) await this.#flushing;
|
|
532
682
|
this.pool.close();
|
|
533
|
-
this.loaders.context.cache.clear();
|
|
534
683
|
}
|
|
535
684
|
#assertAlive() {
|
|
536
685
|
if (this.#shutdown) throw new ClientError("client is shut down");
|
|
@@ -543,30 +692,38 @@ var Client = class {
|
|
|
543
692
|
#wantObserve(flag) {
|
|
544
693
|
return flag !== false;
|
|
545
694
|
}
|
|
546
|
-
/**
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
*/
|
|
550
|
-
observe(event) {
|
|
551
|
-
this.#ingestMeta(event);
|
|
552
|
-
if (!this.#persistEvents) return;
|
|
553
|
-
this.#persistQueue.push(event);
|
|
554
|
-
this.#armFlush();
|
|
695
|
+
/** Ingest one inbound event: index (with relay URL) → meta → persistence. */
|
|
696
|
+
observe(event, relayUrl) {
|
|
697
|
+
this.#ingest(event, relayUrl);
|
|
555
698
|
}
|
|
556
|
-
/**
|
|
699
|
+
/** Ingest many events (deduped by id, order preserved) as one persist batch. */
|
|
557
700
|
observeAll(events) {
|
|
558
701
|
const seen = /* @__PURE__ */ new Set();
|
|
559
702
|
for (const event of events) {
|
|
560
703
|
if (seen.has(event.id)) continue;
|
|
561
704
|
seen.add(event.id);
|
|
562
|
-
this.#
|
|
563
|
-
if (this.#persistEvents) this.#persistQueue.push(event);
|
|
705
|
+
this.#ingest(event);
|
|
564
706
|
}
|
|
565
|
-
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* The single inbound-event pipeline: reactive index add (with the source relay URL recorded in
|
|
710
|
+
* seenOn), gossip routing meta, then the persistence decision. `persist: false` skips the storage
|
|
711
|
+
* queue for callers that persist via their own awaited `putMany` (down-sync); `meta: false` is a
|
|
712
|
+
* pure sighting/index add.
|
|
713
|
+
*/
|
|
714
|
+
#ingest(event, relayUrl, opts) {
|
|
715
|
+
this.index.add(event, relayUrl);
|
|
716
|
+
if (opts?.meta !== false) this.#ingestMeta(event);
|
|
717
|
+
if (opts?.persist === false || !this.#persistEvents) return;
|
|
718
|
+
this.#persistQueue.push(event);
|
|
719
|
+
this.#armFlush();
|
|
720
|
+
}
|
|
721
|
+
/** Record a relay sighting for an id already in the index. */
|
|
722
|
+
#markSeen(id, relayUrl) {
|
|
723
|
+
this.index.markSeen(id, relayUrl);
|
|
566
724
|
}
|
|
567
725
|
#ingestMeta(event) {
|
|
568
726
|
this.gossip.ingest(event);
|
|
569
|
-
if (event.kind === Kind.Metadata || event.kind === Kind.Contacts || event.kind === Kind.MuteList || event.kind === Kind.RelayList || event.kind === Kind.DirectMessageRelaysList) this.loaders.context.cache.putIfNewer(event);
|
|
570
727
|
}
|
|
571
728
|
#armFlush() {
|
|
572
729
|
if (this.#flushing) return;
|
|
@@ -581,8 +738,8 @@ var Client = class {
|
|
|
581
738
|
const batch = this.#persistQueue.splice(0);
|
|
582
739
|
try {
|
|
583
740
|
await this.storage.putMany(batch);
|
|
584
|
-
} catch (
|
|
585
|
-
this.onstorageerror?.(toStorageError(
|
|
741
|
+
} catch (error) {
|
|
742
|
+
invokeSafely(() => this.onstorageerror?.(toStorageError(error)));
|
|
586
743
|
}
|
|
587
744
|
}
|
|
588
745
|
} finally {
|
|
@@ -591,13 +748,13 @@ var Client = class {
|
|
|
591
748
|
}
|
|
592
749
|
}
|
|
593
750
|
/**
|
|
594
|
-
* Explicitly load kind:10002 and kind:10050 lists for pubkeys and feed gossip.
|
|
595
|
-
*
|
|
751
|
+
* Explicitly load kind:10002 and kind:10050 lists for pubkeys and feed gossip. Does not
|
|
752
|
+
* background-fetch unknown authors on every subscribe (predictable DX).
|
|
596
753
|
*/
|
|
597
754
|
async hydrateGossip(pubkeys, opts) {
|
|
598
755
|
this.#assertAlive();
|
|
599
756
|
await Promise.all(pubkeys.map(async (pk) => {
|
|
600
|
-
const style = opts?.force ? "force" : "default";
|
|
757
|
+
const style = opts?.force === true ? "force" : "default";
|
|
601
758
|
const [relayList, dmList] = await Promise.all([this.loaders.relayList(pk, {
|
|
602
759
|
hints: opts?.hints,
|
|
603
760
|
style
|
|
@@ -610,8 +767,8 @@ var Client = class {
|
|
|
610
767
|
}));
|
|
611
768
|
}
|
|
612
769
|
/**
|
|
613
|
-
* Create an outbox-model feed for the given authors (NIP-65 write relays).
|
|
614
|
-
*
|
|
770
|
+
* Create an outbox-model feed for the given authors (NIP-65 write relays). Live events go through
|
|
771
|
+
* {@link observe}; sync persists via applySync.
|
|
615
772
|
*/
|
|
616
773
|
outbox(opts) {
|
|
617
774
|
this.#assertAlive();
|
|
@@ -624,22 +781,27 @@ var Client = class {
|
|
|
624
781
|
kinds: opts.kinds,
|
|
625
782
|
onEvent: opts.onEvent,
|
|
626
783
|
maxRelaysPerAuthor: opts.maxRelaysPerAuthor,
|
|
627
|
-
observe: (event) => this.observe(event),
|
|
784
|
+
observe: (event, relayUrl) => this.observe(event, relayUrl),
|
|
785
|
+
seen: (event, relayUrl) => this.#ingest(event, relayUrl, {
|
|
786
|
+
persist: false,
|
|
787
|
+
meta: false
|
|
788
|
+
}),
|
|
628
789
|
applySync: async (events) => {
|
|
629
790
|
if (!this.#persistEvents) {
|
|
630
|
-
for (const event of events) this.#
|
|
791
|
+
for (const event of events) this.#ingest(event, void 0, { persist: false });
|
|
631
792
|
return [];
|
|
632
793
|
}
|
|
633
794
|
const results = await this.storage.putMany(events);
|
|
634
795
|
const applied = [];
|
|
635
|
-
for (
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
796
|
+
for (const [i, event] of events.entries()) {
|
|
797
|
+
const result = results[i];
|
|
798
|
+
if (result === "rejected" || result === "ephemeral" || result === "invalid") continue;
|
|
799
|
+
this.#ingest(event, void 0, { persist: false });
|
|
800
|
+
applied.push(event);
|
|
639
801
|
}
|
|
640
802
|
return applied;
|
|
641
803
|
},
|
|
642
|
-
hydrate: (pubkeys) => this.hydrateGossip(pubkeys)
|
|
804
|
+
hydrate: async (pubkeys) => this.hydrateGossip(pubkeys)
|
|
643
805
|
});
|
|
644
806
|
}
|
|
645
807
|
async getPublicKey() {
|
|
@@ -663,27 +825,26 @@ var Client = class {
|
|
|
663
825
|
});
|
|
664
826
|
}
|
|
665
827
|
/**
|
|
666
|
-
* Sign (if builder) and publish to relays.
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
* On any successful OK, observes the event into storage/gossip.
|
|
828
|
+
* Sign (if builder) and publish to relays. With `gossip: true`, prefers the author's NIP-65
|
|
829
|
+
* outbox relays, tagged `p` inboxes, and up to 5 `e`/`a` tag relay hints when known. On any
|
|
830
|
+
* successful OK, observes the event into storage/gossip.
|
|
670
831
|
*/
|
|
671
832
|
async publish(eventOrBuilder, opts) {
|
|
672
833
|
this.#assertAlive();
|
|
673
834
|
const event = eventOrBuilder instanceof EventBuilder ? await this.signEventBuilder(eventOrBuilder) : eventOrBuilder;
|
|
674
835
|
let relays = opts?.relays;
|
|
675
836
|
if (!relays && isGiftWrapKind(event.kind)) relays = giftWrapRelays(this.gossip, event);
|
|
676
|
-
else if (!relays && opts?.gossip) {
|
|
837
|
+
else if (!relays && opts?.gossip === true) {
|
|
677
838
|
const urls = [];
|
|
678
839
|
const add = (list) => {
|
|
679
840
|
for (const url of list) if (!urls.includes(url)) urls.push(url);
|
|
680
841
|
};
|
|
681
842
|
add(this.gossip.outboxRelays(event.pubkey));
|
|
682
|
-
for (const tag of event.tags) if (tag[0] === "p" && tag[1]) add(this.gossip.inboxRelays(tag[1]));
|
|
843
|
+
for (const tag of event.tags) if (tag[0] === "p" && tag[1] !== void 0) add(this.gossip.inboxRelays(tag[1]));
|
|
683
844
|
let hintCount = 0;
|
|
684
845
|
for (const tag of event.tags) {
|
|
685
846
|
if (hintCount >= 5) break;
|
|
686
|
-
if (tag[0] !== "e" && tag[0] !== "a" ||
|
|
847
|
+
if (tag[0] !== "e" && tag[0] !== "a" || tag[2] === void 0) continue;
|
|
687
848
|
try {
|
|
688
849
|
const url = normalizeURL(tag[2]);
|
|
689
850
|
if (!urls.includes(url)) {
|
|
@@ -695,46 +856,57 @@ var Client = class {
|
|
|
695
856
|
if (urls.length > 0) relays = urls;
|
|
696
857
|
}
|
|
697
858
|
const results = await this.pool.publish(this.#defaultRelays(relays), event, { timeoutMs: opts?.timeoutMs });
|
|
698
|
-
if (results.some((r) => r.result?.ok) && this.#wantObserve(opts?.observe)) this.observe(event);
|
|
859
|
+
if (results.some((r) => r.result?.ok === true) && this.#wantObserve(opts?.observe)) this.observe(event);
|
|
699
860
|
return results;
|
|
700
861
|
}
|
|
701
862
|
/**
|
|
702
|
-
* Fetch events. With `gossip: true`, routes filters by NIP-65 when possible.
|
|
703
|
-
*
|
|
863
|
+
* Fetch events. With `gossip: true`, routes filters by NIP-65 when possible. With `localFirst:
|
|
864
|
+
* true`, merges storage hits with network results.
|
|
704
865
|
*/
|
|
705
866
|
async fetchEvents(filter, opts) {
|
|
706
867
|
this.#assertAlive();
|
|
707
868
|
const filters = canonicalizeFilters(Array.isArray(filter) ? filter : [filter]);
|
|
708
869
|
const shouldObserve = this.#wantObserve(opts?.observe);
|
|
709
|
-
const
|
|
710
|
-
if (opts?.localFirst) try {
|
|
870
|
+
const candidates = [];
|
|
871
|
+
if (opts?.localFirst === true) try {
|
|
711
872
|
const local = await this.storage.query(filters);
|
|
712
|
-
for (const e of local)
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
byId.set(e.id, e);
|
|
719
|
-
if (shouldObserve) this.observe(e);
|
|
873
|
+
for (const e of local) {
|
|
874
|
+
candidates.push(e);
|
|
875
|
+
if (shouldObserve) this.#ingest(e, void 0, {
|
|
876
|
+
persist: false,
|
|
877
|
+
meta: false
|
|
878
|
+
});
|
|
720
879
|
}
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
ingest(await this.pool.fetch(this.#defaultRelays(opts?.relays), filters, {
|
|
724
|
-
timeoutMs: opts?.timeoutMs,
|
|
725
|
-
signal: opts?.signal
|
|
726
|
-
}));
|
|
727
|
-
return sortedEvents([...byId.values()]);
|
|
880
|
+
} catch (error) {
|
|
881
|
+
invokeSafely(() => this.onstorageerror?.(toStorageError(error)));
|
|
728
882
|
}
|
|
729
|
-
|
|
883
|
+
const onevent = (event, relayUrl) => {
|
|
884
|
+
if (shouldObserve) this.#ingest(event, relayUrl, {
|
|
885
|
+
persist: false,
|
|
886
|
+
meta: false
|
|
887
|
+
});
|
|
888
|
+
opts?.onevent?.(event, relayUrl);
|
|
889
|
+
};
|
|
890
|
+
const batch = opts?.gossip !== true || opts.relays !== void 0 ? await this.pool.fetch(this.#defaultRelays(opts?.relays), filters, {
|
|
730
891
|
timeoutMs: opts?.timeoutMs,
|
|
731
|
-
signal: opts?.signal
|
|
732
|
-
|
|
733
|
-
|
|
892
|
+
signal: opts?.signal,
|
|
893
|
+
onevent
|
|
894
|
+
}) : await fetchGossip(this.pool, this.gossip, filters, () => this.#defaultRelays(), {
|
|
895
|
+
timeoutMs: opts?.timeoutMs,
|
|
896
|
+
signal: opts?.signal,
|
|
897
|
+
onevent
|
|
898
|
+
});
|
|
899
|
+
for (const e of batch) {
|
|
900
|
+
candidates.push(e);
|
|
901
|
+
if (shouldObserve) this.observe(e);
|
|
902
|
+
}
|
|
903
|
+
const tmp = new MemoryIndex();
|
|
904
|
+
const ephemeral = /* @__PURE__ */ new Map();
|
|
905
|
+
const merged = opts?.localFirst === true ? [...this.index.query(filters), ...candidates] : candidates;
|
|
906
|
+
for (const e of merged) if (tmp.put(e) === "ephemeral" && matchFilters(filters, e)) ephemeral.set(e.id, e);
|
|
907
|
+
return sortedEvents([...tmp.query(filters), ...ephemeral.values()]);
|
|
734
908
|
}
|
|
735
|
-
/**
|
|
736
|
-
* Query local storage only (no network).
|
|
737
|
-
*/
|
|
909
|
+
/** Query local storage only (no network). */
|
|
738
910
|
async queryLocal(filter) {
|
|
739
911
|
this.#assertAlive();
|
|
740
912
|
const filters = canonicalizeFilters(Array.isArray(filter) ? filter : [filter]);
|
|
@@ -744,12 +916,17 @@ var Client = class {
|
|
|
744
916
|
this.#assertAlive();
|
|
745
917
|
const filters = canonicalizeFilters(Array.isArray(filter) ? filter : [filter]);
|
|
746
918
|
const shouldObserve = this.#wantObserve(opts?.observe);
|
|
747
|
-
const wrapEvent = (event) => {
|
|
748
|
-
if (shouldObserve) this.observe(event);
|
|
749
|
-
opts?.onevent?.(event);
|
|
919
|
+
const wrapEvent = (event, relayUrl) => {
|
|
920
|
+
if (shouldObserve) this.observe(event, relayUrl);
|
|
921
|
+
opts?.onevent?.(event, relayUrl);
|
|
922
|
+
};
|
|
923
|
+
const wrapReceived = (id, relayUrl) => {
|
|
924
|
+
if (shouldObserve) this.#markSeen(id, relayUrl);
|
|
925
|
+
opts?.receivedEvent?.(id, relayUrl);
|
|
750
926
|
};
|
|
751
|
-
if (
|
|
927
|
+
if (opts?.gossip !== true || opts.relays !== void 0) return this.pool.subscribe(this.#defaultRelays(opts?.relays), filters, {
|
|
752
928
|
onevent: wrapEvent,
|
|
929
|
+
receivedEvent: wrapReceived,
|
|
753
930
|
oneose: opts?.oneose,
|
|
754
931
|
onclose: opts?.onclose,
|
|
755
932
|
signal: opts?.signal,
|
|
@@ -758,6 +935,7 @@ var Client = class {
|
|
|
758
935
|
});
|
|
759
936
|
return subscribeGossip(this.pool, this.gossip, filters, () => this.#defaultRelays(), {
|
|
760
937
|
onevent: wrapEvent,
|
|
938
|
+
receivedEvent: wrapReceived,
|
|
761
939
|
oneose: opts?.oneose,
|
|
762
940
|
onclose: opts?.onclose,
|
|
763
941
|
signal: opts?.signal,
|
|
@@ -769,21 +947,20 @@ var Client = class {
|
|
|
769
947
|
return requireNip59Crypto(this.#signer);
|
|
770
948
|
}
|
|
771
949
|
#throwIfAborted(signal) {
|
|
772
|
-
|
|
773
|
-
if (signal.reason instanceof Error) throw signal.reason;
|
|
774
|
-
throw new ClientError("aborted");
|
|
950
|
+
throwIfAborted(signal);
|
|
775
951
|
}
|
|
776
952
|
#dmDeps() {
|
|
777
953
|
return {
|
|
778
954
|
pool: this.pool,
|
|
779
955
|
gossip: this.gossip,
|
|
780
|
-
hydrateGossip: (pubkeys) => this.hydrateGossip(pubkeys),
|
|
781
|
-
|
|
956
|
+
hydrateGossip: async (pubkeys) => this.hydrateGossip(pubkeys),
|
|
957
|
+
ingest: (event, relayUrl) => this.#ingest(event, relayUrl),
|
|
958
|
+
markSeen: (id, relayUrl) => this.#markSeen(id, relayUrl),
|
|
782
959
|
assertAlive: () => this.#assertAlive(),
|
|
783
960
|
requireNip59Crypto: () => this.#requireNip59Crypto(),
|
|
784
961
|
throwIfAborted: (signal) => this.#throwIfAborted(signal),
|
|
785
962
|
wantObserve: (flag) => this.#wantObserve(flag),
|
|
786
|
-
publish: (eventOrBuilder, opts) => this.publish(eventOrBuilder, opts)
|
|
963
|
+
publish: async (eventOrBuilder, opts) => this.publish(eventOrBuilder, opts)
|
|
787
964
|
};
|
|
788
965
|
}
|
|
789
966
|
#syncDeps() {
|
|
@@ -794,7 +971,8 @@ var Client = class {
|
|
|
794
971
|
assertAlive: () => this.#assertAlive(),
|
|
795
972
|
throwIfAborted: (signal) => this.#throwIfAborted(signal),
|
|
796
973
|
wantObserve: (flag) => this.#wantObserve(flag),
|
|
797
|
-
|
|
974
|
+
ingest: (event, relayUrl, opts) => this.#ingest(event, relayUrl, opts),
|
|
975
|
+
markSeen: (id, relayUrl) => this.#markSeen(id, relayUrl),
|
|
798
976
|
defaultRelays: (urls) => this.#defaultRelays(urls)
|
|
799
977
|
};
|
|
800
978
|
}
|
|
@@ -812,18 +990,17 @@ var Client = class {
|
|
|
812
990
|
return subscribePrivateMessages(this.#dmDeps(), opts);
|
|
813
991
|
}
|
|
814
992
|
/**
|
|
815
|
-
* NIP-77 sync against one relay: reconcile, then optionally upload
|
|
816
|
-
*
|
|
817
|
-
* `
|
|
818
|
-
* `persistEvents: false` skips putMany, still ingestMeta when observe is on.
|
|
993
|
+
* NIP-77 sync against one relay: reconcile, then optionally upload local-only events and/or
|
|
994
|
+
* download remote-only events. `observe: false` skips putMany and ingest; received ids are still
|
|
995
|
+
* listed. `persistEvents: false` skips putMany, still ingests when observe is on.
|
|
819
996
|
*/
|
|
820
997
|
async syncToRelay(url, filter, opts) {
|
|
821
998
|
return syncToRelay(this.#syncDeps(), url, filter, opts);
|
|
822
999
|
}
|
|
823
1000
|
/**
|
|
824
|
-
* NIP-77 sync against the given relays (or Client default relays).
|
|
825
|
-
*
|
|
826
|
-
*
|
|
1001
|
+
* NIP-77 sync against the given relays (or Client default relays). Independent sessions run in
|
|
1002
|
+
* parallel. Fulfilled summaries are merged; if every relay rejects, throws the first rejection in
|
|
1003
|
+
* URL order.
|
|
827
1004
|
*/
|
|
828
1005
|
async sync(filter, opts) {
|
|
829
1006
|
return sync(this.#syncDeps(), filter, opts);
|
|
@@ -832,4 +1009,4 @@ var Client = class {
|
|
|
832
1009
|
//#endregion
|
|
833
1010
|
export { ClientBuilder as i, ClientError as n, SyncDirection as r, Client as t };
|
|
834
1011
|
|
|
835
|
-
//# sourceMappingURL=client-
|
|
1012
|
+
//# sourceMappingURL=client-5DhgYV7P.mjs.map
|