@qntx/nostr 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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-BWsX9Cv1.mjs} +354 -195
- package/dist/client-BWsX9Cv1.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-B425CAcQ.d.mts +328 -0
- package/dist/index-BVyfUBuG.d.mts +181 -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-CUIvFQdE.d.mts +73 -0
- package/dist/index-JdaS_uce.d.mts +207 -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/{nostr_crypto_wasm_bg-hi5aXi5P.mjs → nostr_crypto_wasm_bg-Czrdty7Q.mjs} +1 -1
- package/dist/nostr_crypto_wasm_bg.wasm +0 -0
- package/dist/{pool-C_j1OmBB.mjs → pool-XmAGaufS.mjs} +611 -487
- package/dist/pool-XmAGaufS.mjs.map +1 -0
- package/dist/pool-tEr7c5bG.d.mts +302 -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 +125 -0
- package/dist/testing.mjs +957 -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 +55 -56
- package/dist/wasm.mjs.map +1 -1
- package/dist/websocket-CmeqvAkp.d.mts +27 -0
- package/package.json +31 -9
- 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-XmAGaufS.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,46 @@ 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
|
+
}
|
|
75
127
|
enableReconnect(enabled) {
|
|
76
128
|
this.#opts = {
|
|
77
129
|
...this.#opts,
|
|
@@ -101,8 +153,8 @@ var ClientBuilder = class {
|
|
|
101
153
|
return this;
|
|
102
154
|
}
|
|
103
155
|
/**
|
|
104
|
-
* Local event store. Defaults to {@link MemoryEventStore}.
|
|
105
|
-
*
|
|
156
|
+
* Local event store. Defaults to {@link MemoryEventStore}. Browser apps that want persistence must
|
|
157
|
+
* pass {@link IndexedDbEventStore} and `await open()`.
|
|
106
158
|
*/
|
|
107
159
|
storage(store) {
|
|
108
160
|
this.#opts = {
|
|
@@ -111,6 +163,17 @@ var ClientBuilder = class {
|
|
|
111
163
|
};
|
|
112
164
|
return this;
|
|
113
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Synchronous reactive index mirroring ingested events. Defaults to a new
|
|
168
|
+
* {@link ReactiveEventStore}.
|
|
169
|
+
*/
|
|
170
|
+
index(index) {
|
|
171
|
+
this.#opts = {
|
|
172
|
+
...this.#opts,
|
|
173
|
+
index
|
|
174
|
+
};
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
114
177
|
/** When false, Client will not auto-write events to storage. Default true. */
|
|
115
178
|
persistEvents(enabled) {
|
|
116
179
|
this.#opts = {
|
|
@@ -134,17 +197,18 @@ var ClientBuilder = class {
|
|
|
134
197
|
return this;
|
|
135
198
|
}
|
|
136
199
|
build() {
|
|
137
|
-
return
|
|
200
|
+
return this.#newClient(this.#opts);
|
|
138
201
|
}
|
|
139
202
|
};
|
|
140
203
|
//#endregion
|
|
141
204
|
//#region src/client/dm.ts
|
|
142
205
|
function giftWrapRelays(gossip, event) {
|
|
143
206
|
const targets = [];
|
|
144
|
-
for (const tag of event.tags) if (tag[0] === "p" && tag[1]) targets.push(tag[1].toLowerCase());
|
|
207
|
+
for (const tag of event.tags) if (tag[0] === "p" && tag[1] !== void 0 && tag[1] !== "") targets.push(tag[1].toLowerCase());
|
|
145
208
|
if (targets.length === 0) throw new Nip17Error("gift wrap has no p-tag recipient");
|
|
146
|
-
|
|
147
|
-
|
|
209
|
+
const [target] = targets;
|
|
210
|
+
if (targets.length !== 1 || target === void 0) throw new Nip17Error("gift wrap must have exactly one p-tag recipient");
|
|
211
|
+
const relays = gossip.dmRelays(target);
|
|
148
212
|
if (relays.length === 0) throw new Nip17Error("no kind 10050 in gossip; use sendPrivateMessage or pass relays");
|
|
149
213
|
return relays;
|
|
150
214
|
}
|
|
@@ -174,7 +238,7 @@ async function sendPrivateMessage(deps, recipients, content, opts) {
|
|
|
174
238
|
wraps: await Promise.all(wraps.map(async ({ recipient, wrap }) => {
|
|
175
239
|
const relays = requireDmRelays(recipient, deps.gossip.dmRelays(recipient));
|
|
176
240
|
const results = await deps.pool.publish(relays, wrap, { timeoutMs: opts?.timeoutMs });
|
|
177
|
-
if (results.some((r) => r.result?.ok) && deps.wantObserve(opts?.observe)) deps.
|
|
241
|
+
if (results.some((r) => r.result?.ok === true) && deps.wantObserve(opts?.observe)) deps.ingest(wrap);
|
|
178
242
|
return {
|
|
179
243
|
recipient,
|
|
180
244
|
wrap,
|
|
@@ -191,6 +255,7 @@ async function fetchPrivateMessages(deps, opts) {
|
|
|
191
255
|
await deps.hydrateGossip([self]);
|
|
192
256
|
deps.throwIfAborted(opts?.signal);
|
|
193
257
|
const relays = requireDmRelays(self, deps.gossip.dmRelays(self));
|
|
258
|
+
const urls = /* @__PURE__ */ new Map();
|
|
194
259
|
const events = await deps.pool.fetch(relays, [{
|
|
195
260
|
kinds: [Kind.GiftWrap],
|
|
196
261
|
"#p": [self],
|
|
@@ -198,21 +263,31 @@ async function fetchPrivateMessages(deps, opts) {
|
|
|
198
263
|
until: opts?.until
|
|
199
264
|
}], {
|
|
200
265
|
timeoutMs: opts?.timeoutMs,
|
|
201
|
-
signal: opts?.signal
|
|
266
|
+
signal: opts?.signal,
|
|
267
|
+
onevent: (event, relayUrl) => {
|
|
268
|
+
let set = urls.get(event.id);
|
|
269
|
+
if (!set) urls.set(event.id, set = /* @__PURE__ */ new Set());
|
|
270
|
+
set.add(relayUrl);
|
|
271
|
+
}
|
|
202
272
|
});
|
|
203
273
|
const byRumor = /* @__PURE__ */ new Map();
|
|
204
274
|
for (const wrap of events) try {
|
|
205
275
|
const rumor = await unwrap(crypto, wrap);
|
|
206
|
-
|
|
276
|
+
const wrapUrls = urls.get(wrap.id);
|
|
277
|
+
const firstUrl = wrapUrls?.values().next().value;
|
|
278
|
+
if (deps.wantObserve(opts?.observe)) {
|
|
279
|
+
deps.ingest(wrap, firstUrl);
|
|
280
|
+
if (wrapUrls) {
|
|
281
|
+
for (const url of wrapUrls) if (url !== firstUrl) deps.markSeen(wrap.id, url);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
207
284
|
byRumor.set(rumor.id, {
|
|
208
285
|
wrap,
|
|
209
|
-
rumor
|
|
286
|
+
rumor,
|
|
287
|
+
...firstUrl === void 0 ? {} : { relayUrl: firstUrl }
|
|
210
288
|
});
|
|
211
289
|
} 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
|
-
});
|
|
290
|
+
return [...byRumor.values()].sort((a, b) => itemCompare(a.rumor, b.rumor));
|
|
216
291
|
}
|
|
217
292
|
async function subscribePrivateMessages(deps, opts) {
|
|
218
293
|
deps.assertAlive();
|
|
@@ -223,12 +298,21 @@ async function subscribePrivateMessages(deps, opts) {
|
|
|
223
298
|
deps.throwIfAborted(opts?.signal);
|
|
224
299
|
const relays = requireDmRelays(self, deps.gossip.dmRelays(self));
|
|
225
300
|
const seen = /* @__PURE__ */ new Set();
|
|
301
|
+
const processed = /* @__PURE__ */ new Set();
|
|
302
|
+
const pendingUrls = /* @__PURE__ */ new Map();
|
|
303
|
+
const flushSeen = (wrapId) => {
|
|
304
|
+
processed.add(wrapId);
|
|
305
|
+
const extra = pendingUrls.get(wrapId);
|
|
306
|
+
if (extra === void 0) return;
|
|
307
|
+
pendingUrls.delete(wrapId);
|
|
308
|
+
for (const url of extra) deps.markSeen(wrapId, url);
|
|
309
|
+
};
|
|
226
310
|
let tail = Promise.resolve();
|
|
227
311
|
let closed = false;
|
|
228
312
|
const markClosed = () => {
|
|
229
313
|
closed = true;
|
|
230
314
|
};
|
|
231
|
-
if (opts?.signal?.aborted) markClosed();
|
|
315
|
+
if (opts?.signal?.aborted === true) markClosed();
|
|
232
316
|
else opts?.signal?.addEventListener("abort", markClosed, { once: true });
|
|
233
317
|
const inner = deps.pool.subscribe(relays, [{
|
|
234
318
|
kinds: [Kind.GiftWrap, Kind.GiftWrapEphemeral],
|
|
@@ -242,22 +326,45 @@ async function subscribePrivateMessages(deps, opts) {
|
|
|
242
326
|
opts?.onclose?.(reason);
|
|
243
327
|
},
|
|
244
328
|
eoseTimeoutMs: opts?.eoseTimeoutMs,
|
|
245
|
-
|
|
329
|
+
receivedEvent: (id, relayUrl) => {
|
|
330
|
+
if (closed) return;
|
|
331
|
+
if (processed.has(id)) {
|
|
332
|
+
deps.markSeen(id, relayUrl);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
const list = pendingUrls.get(id);
|
|
336
|
+
if (list === void 0) pendingUrls.set(id, [relayUrl]);
|
|
337
|
+
else if (!list.includes(relayUrl)) list.push(relayUrl);
|
|
338
|
+
},
|
|
339
|
+
onevent: (wrap, relayUrl) => {
|
|
246
340
|
if (closed) return;
|
|
247
|
-
|
|
248
|
-
|
|
341
|
+
const prev = tail;
|
|
342
|
+
let release;
|
|
343
|
+
tail = new Promise((resolve) => {
|
|
344
|
+
release = resolve;
|
|
345
|
+
});
|
|
346
|
+
(async () => {
|
|
347
|
+
await prev;
|
|
249
348
|
try {
|
|
349
|
+
if (closed) return;
|
|
250
350
|
const rumor = await unwrap(crypto, wrap);
|
|
251
351
|
if (closed) return;
|
|
252
|
-
if (seen.has(rumor.id))
|
|
352
|
+
if (seen.has(rumor.id)) {
|
|
353
|
+
flushSeen(wrap.id);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
253
356
|
seen.add(rumor.id);
|
|
254
|
-
if (deps.wantObserve(opts?.observe)) deps.
|
|
357
|
+
if (deps.wantObserve(opts?.observe)) deps.ingest(wrap, relayUrl);
|
|
358
|
+
flushSeen(wrap.id);
|
|
255
359
|
opts?.onevent?.({
|
|
256
360
|
wrap,
|
|
257
|
-
rumor
|
|
361
|
+
rumor,
|
|
362
|
+
relayUrl
|
|
258
363
|
});
|
|
259
|
-
} catch {}
|
|
260
|
-
|
|
364
|
+
} catch {} finally {
|
|
365
|
+
release?.();
|
|
366
|
+
}
|
|
367
|
+
})();
|
|
261
368
|
}
|
|
262
369
|
});
|
|
263
370
|
return { close: (reason) => {
|
|
@@ -266,6 +373,18 @@ async function subscribePrivateMessages(deps, opts) {
|
|
|
266
373
|
} };
|
|
267
374
|
}
|
|
268
375
|
//#endregion
|
|
376
|
+
//#region src/client/types.ts
|
|
377
|
+
/** Direction of a {@link Client.sync} run: upload, download, or both. */
|
|
378
|
+
const SyncDirection = {
|
|
379
|
+
Up: "up",
|
|
380
|
+
Down: "down",
|
|
381
|
+
Both: "both"
|
|
382
|
+
};
|
|
383
|
+
/** Client lifecycle, configuration, or abort failure (not cryptographic). */
|
|
384
|
+
var ClientError = class extends NostrError {
|
|
385
|
+
name = "ClientError";
|
|
386
|
+
};
|
|
387
|
+
//#endregion
|
|
269
388
|
//#region src/client/gossip-io.ts
|
|
270
389
|
/** Remainder is one job on defaults; throw before any REQ when defaults are empty. */
|
|
271
390
|
function jobsForFilters(gossip, filters, defaultRelays) {
|
|
@@ -277,17 +396,21 @@ function jobsForFilters(gossip, filters, defaultRelays) {
|
|
|
277
396
|
urls: [url],
|
|
278
397
|
filters: [sub]
|
|
279
398
|
});
|
|
280
|
-
if (r.remainder)
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
399
|
+
if (r.remainder !== void 0) {
|
|
400
|
+
if (defaults === void 0) throw new ClientError("unrouted remainder without default relays");
|
|
401
|
+
jobs.push({
|
|
402
|
+
urls: defaults,
|
|
403
|
+
filters: [r.remainder]
|
|
404
|
+
});
|
|
405
|
+
}
|
|
284
406
|
}
|
|
285
407
|
return jobs;
|
|
286
408
|
}
|
|
287
|
-
function fetchGossip(pool, gossip, filters, defaultRelays, opts) {
|
|
409
|
+
async function fetchGossip(pool, gossip, filters, defaultRelays, opts) {
|
|
288
410
|
return fetchRouted(pool, jobsForFilters(gossip, filters, defaultRelays), {
|
|
289
411
|
timeoutMs: opts?.timeoutMs,
|
|
290
|
-
signal: opts?.signal
|
|
412
|
+
signal: opts?.signal,
|
|
413
|
+
onevent: opts?.onevent
|
|
291
414
|
});
|
|
292
415
|
}
|
|
293
416
|
function subscribeGossip(pool, gossip, filters, defaultRelays, opts) {
|
|
@@ -299,15 +422,6 @@ function subscribeGossip(pool, gossip, filters, defaultRelays, opts) {
|
|
|
299
422
|
return fanIn(pool, jobs, opts);
|
|
300
423
|
}
|
|
301
424
|
//#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
425
|
//#region src/client/sync.ts
|
|
312
426
|
const SYNC_ID_BATCH = 100;
|
|
313
427
|
const SYNC_UPLOAD_CONCURRENCY = 8;
|
|
@@ -343,19 +457,18 @@ function mergeSyncSummary(into, other) {
|
|
|
343
457
|
};
|
|
344
458
|
}
|
|
345
459
|
/**
|
|
346
|
-
* NIP-77 sync against one relay: reconcile, then optionally upload
|
|
347
|
-
*
|
|
348
|
-
* `
|
|
349
|
-
* `persistEvents: false` skips putMany, still ingestMeta when observe is on.
|
|
460
|
+
* NIP-77 sync against one relay: reconcile, then optionally upload local-only events and/or
|
|
461
|
+
* download remote-only events. `observe: false` skips putMany and ingest; received ids are still
|
|
462
|
+
* listed. `persistEvents: false` skips putMany, still ingests when observe is on.
|
|
350
463
|
*/
|
|
351
464
|
async function syncToRelay(deps, url, filter, opts) {
|
|
352
465
|
deps.assertAlive();
|
|
353
466
|
deps.throwIfAborted(opts?.signal);
|
|
354
467
|
const direction = opts?.direction ?? SyncDirection.Down;
|
|
355
|
-
|
|
356
|
-
const items = await deps.storage.negentropyItems(
|
|
468
|
+
const canon = canonicalizeFilter(filter);
|
|
469
|
+
const items = await deps.storage.negentropyItems(canon);
|
|
357
470
|
const storage = storageFromItems(items);
|
|
358
|
-
const { have, need } = await (await deps.pool.ensureRelay(url, { signal: opts?.signal })).negReconcile(
|
|
471
|
+
const { have, need } = await (await deps.pool.ensureRelay(url, { signal: opts?.signal })).negReconcile(canon, storage, {
|
|
359
472
|
timeoutMs: opts?.timeoutMs,
|
|
360
473
|
signal: opts?.signal
|
|
361
474
|
});
|
|
@@ -367,24 +480,22 @@ async function syncToRelay(deps, url, filter, opts) {
|
|
|
367
480
|
sendFailures: {},
|
|
368
481
|
persistFailures: {}
|
|
369
482
|
};
|
|
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
|
-
}
|
|
483
|
+
if (opts?.dryRun === true) return summary;
|
|
484
|
+
if ((direction === SyncDirection.Up || direction === SyncDirection.Both) && have.length > 0) {
|
|
485
|
+
const found = await deps.storage.query([{ ids: have }]);
|
|
486
|
+
const foundById = new Map(found.map((event) => [event.id, event]));
|
|
487
|
+
for (const id of have) if (!foundById.has(id)) summary.sendFailures[id] = "event not found in local store";
|
|
488
|
+
for (let i = 0; i < found.length; i += SYNC_UPLOAD_CONCURRENCY) {
|
|
489
|
+
const chunk = found.slice(i, i + SYNC_UPLOAD_CONCURRENCY);
|
|
490
|
+
await Promise.all(chunk.map(async (event) => {
|
|
491
|
+
try {
|
|
492
|
+
const results = await deps.pool.publish([url], event, { timeoutMs: opts?.timeoutMs });
|
|
493
|
+
if (results.some((r) => r.result?.ok === true)) summary.sent.push(event.id);
|
|
494
|
+
else summary.sendFailures[event.id] = results[0]?.error ?? results[0]?.result?.message ?? "publish failed";
|
|
495
|
+
} catch (error) {
|
|
496
|
+
summary.sendFailures[event.id] = error instanceof Error ? error.message : String(error);
|
|
497
|
+
}
|
|
498
|
+
}));
|
|
388
499
|
}
|
|
389
500
|
}
|
|
390
501
|
if ((direction === SyncDirection.Down || direction === SyncDirection.Both) && need.length > 0) {
|
|
@@ -392,17 +503,28 @@ async function syncToRelay(deps, url, filter, opts) {
|
|
|
392
503
|
for (let i = 0; i < need.length; i += SYNC_ID_BATCH) {
|
|
393
504
|
const batch = need.slice(i, i + SYNC_ID_BATCH);
|
|
394
505
|
deps.throwIfAborted(opts?.signal);
|
|
506
|
+
const urlsById = /* @__PURE__ */ new Map();
|
|
395
507
|
const events = await deps.pool.fetch([url], [{ ids: batch }], {
|
|
396
508
|
timeoutMs: opts?.timeoutMs,
|
|
397
|
-
signal: opts?.signal
|
|
509
|
+
signal: opts?.signal,
|
|
510
|
+
onevent: shouldObserve ? (event, relayUrl) => {
|
|
511
|
+
const urls = urlsById.get(event.id);
|
|
512
|
+
if (urls === void 0) urlsById.set(event.id, [relayUrl]);
|
|
513
|
+
else if (!urls.includes(relayUrl)) urls.push(relayUrl);
|
|
514
|
+
} : void 0
|
|
398
515
|
});
|
|
399
516
|
if (!shouldObserve) {
|
|
400
517
|
for (const event of events) summary.received.push(event.id);
|
|
401
518
|
continue;
|
|
402
519
|
}
|
|
520
|
+
const ingestAll = (event) => {
|
|
521
|
+
const urls = urlsById.get(event.id) ?? [];
|
|
522
|
+
deps.ingest(event, urls[0], { persist: false });
|
|
523
|
+
for (const url of urls.slice(1)) deps.markSeen(event.id, url);
|
|
524
|
+
};
|
|
403
525
|
if (!deps.persistEvents) {
|
|
404
526
|
for (const event of events) {
|
|
405
|
-
|
|
527
|
+
ingestAll(event);
|
|
406
528
|
summary.received.push(event.id);
|
|
407
529
|
}
|
|
408
530
|
continue;
|
|
@@ -410,15 +532,14 @@ async function syncToRelay(deps, url, filter, opts) {
|
|
|
410
532
|
let results;
|
|
411
533
|
try {
|
|
412
534
|
results = await deps.storage.putMany(events);
|
|
413
|
-
} catch (
|
|
414
|
-
const message =
|
|
535
|
+
} catch (error) {
|
|
536
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
415
537
|
for (const event of events) summary.persistFailures[event.id] = message;
|
|
416
538
|
break;
|
|
417
539
|
}
|
|
418
|
-
for (
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
deps.ingestMeta(event);
|
|
540
|
+
for (const [j, event] of events.entries()) {
|
|
541
|
+
if (results[j] === "rejected" || results[j] === "invalid") continue;
|
|
542
|
+
ingestAll(event);
|
|
422
543
|
summary.received.push(event.id);
|
|
423
544
|
}
|
|
424
545
|
}
|
|
@@ -426,14 +547,14 @@ async function syncToRelay(deps, url, filter, opts) {
|
|
|
426
547
|
return summary;
|
|
427
548
|
}
|
|
428
549
|
/**
|
|
429
|
-
* NIP-77 sync against the given relays (or Client default relays).
|
|
430
|
-
*
|
|
431
|
-
*
|
|
550
|
+
* NIP-77 sync against the given relays (or Client default relays). Independent sessions run in
|
|
551
|
+
* parallel. Fulfilled summaries are merged; if every relay rejects, throws the first rejection in
|
|
552
|
+
* URL order.
|
|
432
553
|
*/
|
|
433
554
|
async function sync(deps, filter, opts) {
|
|
434
555
|
deps.assertAlive();
|
|
435
556
|
const urls = deps.defaultRelays(opts?.relays ? [...opts.relays] : void 0);
|
|
436
|
-
const results = await Promise.allSettled(urls.map((url) => syncToRelay(deps, url, filter, opts)));
|
|
557
|
+
const results = await Promise.allSettled(urls.map(async (url) => syncToRelay(deps, url, filter, opts)));
|
|
437
558
|
let merged = emptySummary();
|
|
438
559
|
let fulfilled = 0;
|
|
439
560
|
let firstRejection;
|
|
@@ -446,14 +567,13 @@ async function sync(deps, filter, opts) {
|
|
|
446
567
|
}
|
|
447
568
|
//#endregion
|
|
448
569
|
//#region src/client/client.ts
|
|
449
|
-
/**
|
|
450
|
-
|
|
451
|
-
*/
|
|
452
|
-
var Client = class {
|
|
570
|
+
/** Layer-5 facade: signer + default relays + pool + loaders + gossip + event store. */
|
|
571
|
+
var Client = class Client {
|
|
453
572
|
pool;
|
|
454
573
|
loaders;
|
|
455
574
|
gossip;
|
|
456
575
|
storage;
|
|
576
|
+
index;
|
|
457
577
|
onstorageerror;
|
|
458
578
|
#signer;
|
|
459
579
|
#relays;
|
|
@@ -463,12 +583,16 @@ var Client = class {
|
|
|
463
583
|
#flushing;
|
|
464
584
|
constructor(opts = {}) {
|
|
465
585
|
this.#signer = opts.signer;
|
|
466
|
-
this.#relays = [
|
|
586
|
+
this.#relays = [];
|
|
587
|
+
for (const raw of opts.relays ?? []) {
|
|
588
|
+
const url = normalizeURL(raw);
|
|
589
|
+
if (!this.#relays.includes(url)) this.#relays.push(url);
|
|
590
|
+
}
|
|
467
591
|
this.gossip = opts.gossip ?? new Gossip();
|
|
468
592
|
this.storage = opts.storage ?? new MemoryEventStore();
|
|
593
|
+
this.index = opts.index ?? new ReactiveEventStore();
|
|
469
594
|
this.#persistEvents = opts.persistEvents ?? true;
|
|
470
|
-
this.onstorageerror = opts.onstorageerror
|
|
471
|
-
const autoAuth = opts.automaticAuth ?? Boolean(opts.signer);
|
|
595
|
+
this.onstorageerror = opts.onstorageerror;
|
|
472
596
|
this.pool = new Pool({
|
|
473
597
|
websocketImplementation: opts.websocketImplementation,
|
|
474
598
|
verifyEvent: opts.verifyEvent,
|
|
@@ -478,25 +602,30 @@ var Client = class {
|
|
|
478
602
|
connectTimeoutMs: opts.connectTimeoutMs,
|
|
479
603
|
publishTimeoutMs: opts.publishTimeoutMs,
|
|
480
604
|
enableReconnect: opts.enableReconnect ?? true,
|
|
481
|
-
|
|
605
|
+
allowInsecure: opts.allowInsecure,
|
|
606
|
+
trustedInsecureUrls: opts.trustedInsecureUrls,
|
|
607
|
+
idleTimeoutMs: opts.idleTimeoutMs,
|
|
608
|
+
maxRelays: opts.maxRelays,
|
|
609
|
+
pinnedUrls: opts.pinnedUrls,
|
|
610
|
+
automaticallyAuth: opts.automaticAuth ?? true ? () => async (template) => {
|
|
482
611
|
const signer = this.#signer;
|
|
483
|
-
if (!signer)
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
});
|
|
490
|
-
};
|
|
612
|
+
if (!signer) throw new NoSignerError("no signer configured for AUTH");
|
|
613
|
+
const pk = await signer.getPublicKey();
|
|
614
|
+
return signer.signEvent({
|
|
615
|
+
...template,
|
|
616
|
+
pubkey: pk
|
|
617
|
+
});
|
|
491
618
|
} : void 0
|
|
492
619
|
});
|
|
493
620
|
this.loaders = createLoaders({
|
|
494
621
|
pool: this.pool,
|
|
495
|
-
relays: this.#relays
|
|
622
|
+
relays: this.#relays,
|
|
623
|
+
index: this.index,
|
|
624
|
+
ingest: (event, relayUrl) => this.#ingest(event, relayUrl)
|
|
496
625
|
});
|
|
497
626
|
}
|
|
498
627
|
static builder() {
|
|
499
|
-
return new ClientBuilder();
|
|
628
|
+
return new ClientBuilder((opts) => new Client(opts));
|
|
500
629
|
}
|
|
501
630
|
get signer() {
|
|
502
631
|
return this.#signer;
|
|
@@ -509,28 +638,30 @@ var Client = class {
|
|
|
509
638
|
}
|
|
510
639
|
setSigner(signer) {
|
|
511
640
|
this.#signer = signer;
|
|
641
|
+
this.pool.resetAuth();
|
|
512
642
|
}
|
|
513
643
|
addRelay(url) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
this.
|
|
644
|
+
const normalized = normalizeURL(url);
|
|
645
|
+
if (!this.#relays.includes(normalized)) {
|
|
646
|
+
this.#relays.push(normalized);
|
|
647
|
+
this.loaders.addRelay(normalized);
|
|
517
648
|
}
|
|
518
649
|
}
|
|
519
650
|
removeRelay(url) {
|
|
520
|
-
|
|
521
|
-
this.
|
|
522
|
-
this.
|
|
651
|
+
const normalized = normalizeURL(url);
|
|
652
|
+
this.#relays = this.#relays.filter((r) => r !== normalized);
|
|
653
|
+
this.loaders.removeRelay(normalized);
|
|
654
|
+
this.pool.close([normalized]);
|
|
523
655
|
}
|
|
524
656
|
/** Connect all configured relays (best-effort; failures are ignored). */
|
|
525
657
|
async connect(opts) {
|
|
526
658
|
this.#assertAlive();
|
|
527
|
-
await Promise.allSettled(this.#relays.map((url) => this.pool.ensureRelay(url, { signal: opts?.signal })));
|
|
659
|
+
await Promise.allSettled(this.#relays.map(async (url) => this.pool.ensureRelay(url, { signal: opts?.signal })));
|
|
528
660
|
}
|
|
529
661
|
async shutdown() {
|
|
530
662
|
this.#shutdown = true;
|
|
531
|
-
while (this.#flushing) await this.#flushing;
|
|
663
|
+
while (this.#flushing !== void 0) await this.#flushing;
|
|
532
664
|
this.pool.close();
|
|
533
|
-
this.loaders.context.cache.clear();
|
|
534
665
|
}
|
|
535
666
|
#assertAlive() {
|
|
536
667
|
if (this.#shutdown) throw new ClientError("client is shut down");
|
|
@@ -543,30 +674,38 @@ var Client = class {
|
|
|
543
674
|
#wantObserve(flag) {
|
|
544
675
|
return flag !== false;
|
|
545
676
|
}
|
|
546
|
-
/**
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
*/
|
|
550
|
-
observe(event) {
|
|
551
|
-
this.#ingestMeta(event);
|
|
552
|
-
if (!this.#persistEvents) return;
|
|
553
|
-
this.#persistQueue.push(event);
|
|
554
|
-
this.#armFlush();
|
|
677
|
+
/** Ingest one inbound event: index (with relay URL) → meta → persistence. */
|
|
678
|
+
observe(event, relayUrl) {
|
|
679
|
+
this.#ingest(event, relayUrl);
|
|
555
680
|
}
|
|
556
|
-
/**
|
|
681
|
+
/** Ingest many events (deduped by id, order preserved) as one persist batch. */
|
|
557
682
|
observeAll(events) {
|
|
558
683
|
const seen = /* @__PURE__ */ new Set();
|
|
559
684
|
for (const event of events) {
|
|
560
685
|
if (seen.has(event.id)) continue;
|
|
561
686
|
seen.add(event.id);
|
|
562
|
-
this.#
|
|
563
|
-
if (this.#persistEvents) this.#persistQueue.push(event);
|
|
687
|
+
this.#ingest(event);
|
|
564
688
|
}
|
|
565
|
-
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* The single inbound-event pipeline: reactive index add (with the source relay URL recorded in
|
|
692
|
+
* seenOn), gossip routing meta, then the persistence decision. `persist: false` skips the storage
|
|
693
|
+
* queue for callers that persist via their own awaited `putMany` (down-sync); `meta: false` is a
|
|
694
|
+
* pure sighting/index add.
|
|
695
|
+
*/
|
|
696
|
+
#ingest(event, relayUrl, opts) {
|
|
697
|
+
this.index.add(event, relayUrl);
|
|
698
|
+
if (opts?.meta !== false) this.#ingestMeta(event);
|
|
699
|
+
if (opts?.persist === false || !this.#persistEvents) return;
|
|
700
|
+
this.#persistQueue.push(event);
|
|
701
|
+
this.#armFlush();
|
|
702
|
+
}
|
|
703
|
+
/** Record a relay sighting for an id already in the index. */
|
|
704
|
+
#markSeen(id, relayUrl) {
|
|
705
|
+
this.index.markSeen(id, relayUrl);
|
|
566
706
|
}
|
|
567
707
|
#ingestMeta(event) {
|
|
568
708
|
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
709
|
}
|
|
571
710
|
#armFlush() {
|
|
572
711
|
if (this.#flushing) return;
|
|
@@ -581,8 +720,8 @@ var Client = class {
|
|
|
581
720
|
const batch = this.#persistQueue.splice(0);
|
|
582
721
|
try {
|
|
583
722
|
await this.storage.putMany(batch);
|
|
584
|
-
} catch (
|
|
585
|
-
this.onstorageerror?.(toStorageError(
|
|
723
|
+
} catch (error) {
|
|
724
|
+
invokeSafely(() => this.onstorageerror?.(toStorageError(error)));
|
|
586
725
|
}
|
|
587
726
|
}
|
|
588
727
|
} finally {
|
|
@@ -591,13 +730,13 @@ var Client = class {
|
|
|
591
730
|
}
|
|
592
731
|
}
|
|
593
732
|
/**
|
|
594
|
-
* Explicitly load kind:10002 and kind:10050 lists for pubkeys and feed gossip.
|
|
595
|
-
*
|
|
733
|
+
* Explicitly load kind:10002 and kind:10050 lists for pubkeys and feed gossip. Does not
|
|
734
|
+
* background-fetch unknown authors on every subscribe (predictable DX).
|
|
596
735
|
*/
|
|
597
736
|
async hydrateGossip(pubkeys, opts) {
|
|
598
737
|
this.#assertAlive();
|
|
599
738
|
await Promise.all(pubkeys.map(async (pk) => {
|
|
600
|
-
const style = opts?.force ? "force" : "default";
|
|
739
|
+
const style = opts?.force === true ? "force" : "default";
|
|
601
740
|
const [relayList, dmList] = await Promise.all([this.loaders.relayList(pk, {
|
|
602
741
|
hints: opts?.hints,
|
|
603
742
|
style
|
|
@@ -610,8 +749,8 @@ var Client = class {
|
|
|
610
749
|
}));
|
|
611
750
|
}
|
|
612
751
|
/**
|
|
613
|
-
* Create an outbox-model feed for the given authors (NIP-65 write relays).
|
|
614
|
-
*
|
|
752
|
+
* Create an outbox-model feed for the given authors (NIP-65 write relays). Live events go through
|
|
753
|
+
* {@link observe}; sync persists via applySync.
|
|
615
754
|
*/
|
|
616
755
|
outbox(opts) {
|
|
617
756
|
this.#assertAlive();
|
|
@@ -624,22 +763,27 @@ var Client = class {
|
|
|
624
763
|
kinds: opts.kinds,
|
|
625
764
|
onEvent: opts.onEvent,
|
|
626
765
|
maxRelaysPerAuthor: opts.maxRelaysPerAuthor,
|
|
627
|
-
observe: (event) => this.observe(event),
|
|
766
|
+
observe: (event, relayUrl) => this.observe(event, relayUrl),
|
|
767
|
+
seen: (event, relayUrl) => this.#ingest(event, relayUrl, {
|
|
768
|
+
persist: false,
|
|
769
|
+
meta: false
|
|
770
|
+
}),
|
|
628
771
|
applySync: async (events) => {
|
|
629
772
|
if (!this.#persistEvents) {
|
|
630
|
-
for (const event of events) this.#
|
|
773
|
+
for (const event of events) this.#ingest(event, void 0, { persist: false });
|
|
631
774
|
return [];
|
|
632
775
|
}
|
|
633
776
|
const results = await this.storage.putMany(events);
|
|
634
777
|
const applied = [];
|
|
635
|
-
for (
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
778
|
+
for (const [i, event] of events.entries()) {
|
|
779
|
+
const result = results[i];
|
|
780
|
+
if (result === "rejected" || result === "ephemeral" || result === "invalid") continue;
|
|
781
|
+
this.#ingest(event, void 0, { persist: false });
|
|
782
|
+
applied.push(event);
|
|
639
783
|
}
|
|
640
784
|
return applied;
|
|
641
785
|
},
|
|
642
|
-
hydrate: (pubkeys) => this.hydrateGossip(pubkeys)
|
|
786
|
+
hydrate: async (pubkeys) => this.hydrateGossip(pubkeys)
|
|
643
787
|
});
|
|
644
788
|
}
|
|
645
789
|
async getPublicKey() {
|
|
@@ -663,27 +807,26 @@ var Client = class {
|
|
|
663
807
|
});
|
|
664
808
|
}
|
|
665
809
|
/**
|
|
666
|
-
* Sign (if builder) and publish to relays.
|
|
667
|
-
*
|
|
668
|
-
*
|
|
669
|
-
* On any successful OK, observes the event into storage/gossip.
|
|
810
|
+
* Sign (if builder) and publish to relays. With `gossip: true`, prefers the author's NIP-65
|
|
811
|
+
* outbox relays, tagged `p` inboxes, and up to 5 `e`/`a` tag relay hints when known. On any
|
|
812
|
+
* successful OK, observes the event into storage/gossip.
|
|
670
813
|
*/
|
|
671
814
|
async publish(eventOrBuilder, opts) {
|
|
672
815
|
this.#assertAlive();
|
|
673
816
|
const event = eventOrBuilder instanceof EventBuilder ? await this.signEventBuilder(eventOrBuilder) : eventOrBuilder;
|
|
674
817
|
let relays = opts?.relays;
|
|
675
818
|
if (!relays && isGiftWrapKind(event.kind)) relays = giftWrapRelays(this.gossip, event);
|
|
676
|
-
else if (!relays && opts?.gossip) {
|
|
819
|
+
else if (!relays && opts?.gossip === true) {
|
|
677
820
|
const urls = [];
|
|
678
821
|
const add = (list) => {
|
|
679
822
|
for (const url of list) if (!urls.includes(url)) urls.push(url);
|
|
680
823
|
};
|
|
681
824
|
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]));
|
|
825
|
+
for (const tag of event.tags) if (tag[0] === "p" && tag[1] !== void 0) add(this.gossip.inboxRelays(tag[1]));
|
|
683
826
|
let hintCount = 0;
|
|
684
827
|
for (const tag of event.tags) {
|
|
685
828
|
if (hintCount >= 5) break;
|
|
686
|
-
if (tag[0] !== "e" && tag[0] !== "a" ||
|
|
829
|
+
if (tag[0] !== "e" && tag[0] !== "a" || tag[2] === void 0) continue;
|
|
687
830
|
try {
|
|
688
831
|
const url = normalizeURL(tag[2]);
|
|
689
832
|
if (!urls.includes(url)) {
|
|
@@ -695,46 +838,57 @@ var Client = class {
|
|
|
695
838
|
if (urls.length > 0) relays = urls;
|
|
696
839
|
}
|
|
697
840
|
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);
|
|
841
|
+
if (results.some((r) => r.result?.ok === true) && this.#wantObserve(opts?.observe)) this.observe(event);
|
|
699
842
|
return results;
|
|
700
843
|
}
|
|
701
844
|
/**
|
|
702
|
-
* Fetch events. With `gossip: true`, routes filters by NIP-65 when possible.
|
|
703
|
-
*
|
|
845
|
+
* Fetch events. With `gossip: true`, routes filters by NIP-65 when possible. With `localFirst:
|
|
846
|
+
* true`, merges storage hits with network results.
|
|
704
847
|
*/
|
|
705
848
|
async fetchEvents(filter, opts) {
|
|
706
849
|
this.#assertAlive();
|
|
707
850
|
const filters = canonicalizeFilters(Array.isArray(filter) ? filter : [filter]);
|
|
708
851
|
const shouldObserve = this.#wantObserve(opts?.observe);
|
|
709
|
-
const
|
|
710
|
-
if (opts?.localFirst) try {
|
|
852
|
+
const candidates = [];
|
|
853
|
+
if (opts?.localFirst === true) try {
|
|
711
854
|
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);
|
|
855
|
+
for (const e of local) {
|
|
856
|
+
candidates.push(e);
|
|
857
|
+
if (shouldObserve) this.#ingest(e, void 0, {
|
|
858
|
+
persist: false,
|
|
859
|
+
meta: false
|
|
860
|
+
});
|
|
720
861
|
}
|
|
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()]);
|
|
862
|
+
} catch (error) {
|
|
863
|
+
invokeSafely(() => this.onstorageerror?.(toStorageError(error)));
|
|
728
864
|
}
|
|
729
|
-
|
|
865
|
+
const onevent = (event, relayUrl) => {
|
|
866
|
+
if (shouldObserve) this.#ingest(event, relayUrl, {
|
|
867
|
+
persist: false,
|
|
868
|
+
meta: false
|
|
869
|
+
});
|
|
870
|
+
opts?.onevent?.(event, relayUrl);
|
|
871
|
+
};
|
|
872
|
+
const batch = opts?.gossip !== true || opts.relays !== void 0 ? await this.pool.fetch(this.#defaultRelays(opts?.relays), filters, {
|
|
730
873
|
timeoutMs: opts?.timeoutMs,
|
|
731
|
-
signal: opts?.signal
|
|
732
|
-
|
|
733
|
-
|
|
874
|
+
signal: opts?.signal,
|
|
875
|
+
onevent
|
|
876
|
+
}) : await fetchGossip(this.pool, this.gossip, filters, () => this.#defaultRelays(), {
|
|
877
|
+
timeoutMs: opts?.timeoutMs,
|
|
878
|
+
signal: opts?.signal,
|
|
879
|
+
onevent
|
|
880
|
+
});
|
|
881
|
+
for (const e of batch) {
|
|
882
|
+
candidates.push(e);
|
|
883
|
+
if (shouldObserve) this.observe(e);
|
|
884
|
+
}
|
|
885
|
+
const tmp = new MemoryIndex();
|
|
886
|
+
const ephemeral = /* @__PURE__ */ new Map();
|
|
887
|
+
const merged = opts?.localFirst === true ? [...this.index.query(filters), ...candidates] : candidates;
|
|
888
|
+
for (const e of merged) if (tmp.put(e) === "ephemeral" && matchFilters(filters, e)) ephemeral.set(e.id, e);
|
|
889
|
+
return sortedEvents([...tmp.query(filters), ...ephemeral.values()]);
|
|
734
890
|
}
|
|
735
|
-
/**
|
|
736
|
-
* Query local storage only (no network).
|
|
737
|
-
*/
|
|
891
|
+
/** Query local storage only (no network). */
|
|
738
892
|
async queryLocal(filter) {
|
|
739
893
|
this.#assertAlive();
|
|
740
894
|
const filters = canonicalizeFilters(Array.isArray(filter) ? filter : [filter]);
|
|
@@ -744,12 +898,17 @@ var Client = class {
|
|
|
744
898
|
this.#assertAlive();
|
|
745
899
|
const filters = canonicalizeFilters(Array.isArray(filter) ? filter : [filter]);
|
|
746
900
|
const shouldObserve = this.#wantObserve(opts?.observe);
|
|
747
|
-
const wrapEvent = (event) => {
|
|
748
|
-
if (shouldObserve) this.observe(event);
|
|
749
|
-
opts?.onevent?.(event);
|
|
901
|
+
const wrapEvent = (event, relayUrl) => {
|
|
902
|
+
if (shouldObserve) this.observe(event, relayUrl);
|
|
903
|
+
opts?.onevent?.(event, relayUrl);
|
|
904
|
+
};
|
|
905
|
+
const wrapReceived = (id, relayUrl) => {
|
|
906
|
+
if (shouldObserve) this.#markSeen(id, relayUrl);
|
|
907
|
+
opts?.receivedEvent?.(id, relayUrl);
|
|
750
908
|
};
|
|
751
|
-
if (
|
|
909
|
+
if (opts?.gossip !== true || opts.relays !== void 0) return this.pool.subscribe(this.#defaultRelays(opts?.relays), filters, {
|
|
752
910
|
onevent: wrapEvent,
|
|
911
|
+
receivedEvent: wrapReceived,
|
|
753
912
|
oneose: opts?.oneose,
|
|
754
913
|
onclose: opts?.onclose,
|
|
755
914
|
signal: opts?.signal,
|
|
@@ -758,6 +917,7 @@ var Client = class {
|
|
|
758
917
|
});
|
|
759
918
|
return subscribeGossip(this.pool, this.gossip, filters, () => this.#defaultRelays(), {
|
|
760
919
|
onevent: wrapEvent,
|
|
920
|
+
receivedEvent: wrapReceived,
|
|
761
921
|
oneose: opts?.oneose,
|
|
762
922
|
onclose: opts?.onclose,
|
|
763
923
|
signal: opts?.signal,
|
|
@@ -769,21 +929,20 @@ var Client = class {
|
|
|
769
929
|
return requireNip59Crypto(this.#signer);
|
|
770
930
|
}
|
|
771
931
|
#throwIfAborted(signal) {
|
|
772
|
-
|
|
773
|
-
if (signal.reason instanceof Error) throw signal.reason;
|
|
774
|
-
throw new ClientError("aborted");
|
|
932
|
+
throwIfAborted(signal);
|
|
775
933
|
}
|
|
776
934
|
#dmDeps() {
|
|
777
935
|
return {
|
|
778
936
|
pool: this.pool,
|
|
779
937
|
gossip: this.gossip,
|
|
780
|
-
hydrateGossip: (pubkeys) => this.hydrateGossip(pubkeys),
|
|
781
|
-
|
|
938
|
+
hydrateGossip: async (pubkeys) => this.hydrateGossip(pubkeys),
|
|
939
|
+
ingest: (event, relayUrl) => this.#ingest(event, relayUrl),
|
|
940
|
+
markSeen: (id, relayUrl) => this.#markSeen(id, relayUrl),
|
|
782
941
|
assertAlive: () => this.#assertAlive(),
|
|
783
942
|
requireNip59Crypto: () => this.#requireNip59Crypto(),
|
|
784
943
|
throwIfAborted: (signal) => this.#throwIfAborted(signal),
|
|
785
944
|
wantObserve: (flag) => this.#wantObserve(flag),
|
|
786
|
-
publish: (eventOrBuilder, opts) => this.publish(eventOrBuilder, opts)
|
|
945
|
+
publish: async (eventOrBuilder, opts) => this.publish(eventOrBuilder, opts)
|
|
787
946
|
};
|
|
788
947
|
}
|
|
789
948
|
#syncDeps() {
|
|
@@ -794,7 +953,8 @@ var Client = class {
|
|
|
794
953
|
assertAlive: () => this.#assertAlive(),
|
|
795
954
|
throwIfAborted: (signal) => this.#throwIfAborted(signal),
|
|
796
955
|
wantObserve: (flag) => this.#wantObserve(flag),
|
|
797
|
-
|
|
956
|
+
ingest: (event, relayUrl, opts) => this.#ingest(event, relayUrl, opts),
|
|
957
|
+
markSeen: (id, relayUrl) => this.#markSeen(id, relayUrl),
|
|
798
958
|
defaultRelays: (urls) => this.#defaultRelays(urls)
|
|
799
959
|
};
|
|
800
960
|
}
|
|
@@ -812,18 +972,17 @@ var Client = class {
|
|
|
812
972
|
return subscribePrivateMessages(this.#dmDeps(), opts);
|
|
813
973
|
}
|
|
814
974
|
/**
|
|
815
|
-
* NIP-77 sync against one relay: reconcile, then optionally upload
|
|
816
|
-
*
|
|
817
|
-
* `
|
|
818
|
-
* `persistEvents: false` skips putMany, still ingestMeta when observe is on.
|
|
975
|
+
* NIP-77 sync against one relay: reconcile, then optionally upload local-only events and/or
|
|
976
|
+
* download remote-only events. `observe: false` skips putMany and ingest; received ids are still
|
|
977
|
+
* listed. `persistEvents: false` skips putMany, still ingests when observe is on.
|
|
819
978
|
*/
|
|
820
979
|
async syncToRelay(url, filter, opts) {
|
|
821
980
|
return syncToRelay(this.#syncDeps(), url, filter, opts);
|
|
822
981
|
}
|
|
823
982
|
/**
|
|
824
|
-
* NIP-77 sync against the given relays (or Client default relays).
|
|
825
|
-
*
|
|
826
|
-
*
|
|
983
|
+
* NIP-77 sync against the given relays (or Client default relays). Independent sessions run in
|
|
984
|
+
* parallel. Fulfilled summaries are merged; if every relay rejects, throws the first rejection in
|
|
985
|
+
* URL order.
|
|
827
986
|
*/
|
|
828
987
|
async sync(filter, opts) {
|
|
829
988
|
return sync(this.#syncDeps(), filter, opts);
|
|
@@ -832,4 +991,4 @@ var Client = class {
|
|
|
832
991
|
//#endregion
|
|
833
992
|
export { ClientBuilder as i, ClientError as n, SyncDirection as r, Client as t };
|
|
834
993
|
|
|
835
|
-
//# sourceMappingURL=client-
|
|
994
|
+
//# sourceMappingURL=client-BWsX9Cv1.mjs.map
|