@qntx/nostr 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +25 -0
- package/dist/builder-BphJa-Y-.d.mts +67 -0
- package/dist/builder-q0VsvnYE.mjs +158 -0
- package/dist/builder-q0VsvnYE.mjs.map +1 -0
- package/dist/client-Bd8B1mYm.mjs +835 -0
- package/dist/client-Bd8B1mYm.mjs.map +1 -0
- package/dist/client.d.mts +2 -0
- package/dist/client.mjs +2 -0
- package/dist/core.d.mts +8 -0
- package/dist/core.mjs +9 -0
- package/dist/error-B49rbDzT.d.mts +7 -0
- package/dist/error-CwBxle6N.mjs +13 -0
- package/dist/error-CwBxle6N.mjs.map +1 -0
- package/dist/error-DZEs5OuZ.mjs +24 -0
- package/dist/error-DZEs5OuZ.mjs.map +1 -0
- package/dist/error-WEFS-R-4.d.mts +20 -0
- package/dist/event-B5D2jxt_.d.mts +108 -0
- package/dist/event-CzoQEFMF.mjs +188 -0
- package/dist/event-CzoQEFMF.mjs.map +1 -0
- package/dist/filter-BxwjEkZp.mjs +113 -0
- package/dist/filter-BxwjEkZp.mjs.map +1 -0
- package/dist/filter-DUEKP3q1.d.mts +40 -0
- package/dist/gossip-3xkRRCm_.mjs +220 -0
- package/dist/gossip-3xkRRCm_.mjs.map +1 -0
- package/dist/gossip-C5RVB1UB.d.mts +71 -0
- package/dist/gossip.d.mts +3 -0
- package/dist/gossip.mjs +2 -0
- package/dist/http-6vevbBlu.d.mts +22 -0
- package/dist/http-D2rhokZo.mjs +24 -0
- package/dist/http-D2rhokZo.mjs.map +1 -0
- package/dist/index-BYMnhRIC.d.mts +161 -0
- package/dist/index-C0C4e-Gn.d.mts +55 -0
- package/dist/index-CaxrMD1A.d.mts +1 -0
- package/dist/index-DaRyjo7O.d.mts +77 -0
- package/dist/index-DgOugrpF.d.mts +275 -0
- package/dist/index-EwL6fHLo.d.mts +267 -0
- package/dist/index-k2YhCgsh.d.mts +57 -0
- package/dist/index.d.mts +22 -0
- package/dist/index.mjs +23 -0
- package/dist/key-BiGxGLhg.d.mts +37 -0
- package/dist/key-Dn-aX4x4.mjs +130 -0
- package/dist/key-Dn-aX4x4.mjs.map +1 -0
- package/dist/kind-BLl4cLOc.mjs +59 -0
- package/dist/kind-BLl4cLOc.mjs.map +1 -0
- package/dist/loaders-BNQszDAn.mjs +767 -0
- package/dist/loaders-BNQszDAn.mjs.map +1 -0
- package/dist/loaders.d.mts +3 -0
- package/dist/loaders.mjs +2 -0
- package/dist/memory-BCmhZPek.mjs +461 -0
- package/dist/memory-BCmhZPek.mjs.map +1 -0
- package/dist/message-C_Uj-Y-c.mjs +179 -0
- package/dist/message-C_Uj-Y-c.mjs.map +1 -0
- package/dist/message-DJC-Jz6T.d.mts +32 -0
- package/dist/nip17-BWq-Kfq5.d.mts +38 -0
- package/dist/nip19-D7905oIv.d.mts +63 -0
- package/dist/nip46-C_DoxiS_.d.mts +51 -0
- package/dist/nip59-DljW_sYh.d.mts +76 -0
- package/dist/nip65-Di5hZRuV.d.mts +21 -0
- package/dist/nip77-ByxlgoQO.d.mts +55 -0
- package/dist/nips/blossom.d.mts +93 -0
- package/dist/nips/blossom.mjs +325 -0
- package/dist/nips/blossom.mjs.map +1 -0
- package/dist/nips/nip04.d.mts +8 -0
- package/dist/nips/nip04.mjs +41 -0
- package/dist/nips/nip04.mjs.map +1 -0
- package/dist/nips/nip05.d.mts +89 -0
- package/dist/nips/nip05.mjs +147 -0
- package/dist/nips/nip05.mjs.map +1 -0
- package/dist/nips/nip10.d.mts +43 -0
- package/dist/nips/nip10.mjs +194 -0
- package/dist/nips/nip10.mjs.map +1 -0
- package/dist/nips/nip11.d.mts +43 -0
- package/dist/nips/nip11.mjs +99 -0
- package/dist/nips/nip11.mjs.map +1 -0
- package/dist/nips/nip13.d.mts +23 -0
- package/dist/nips/nip13.mjs +88 -0
- package/dist/nips/nip13.mjs.map +1 -0
- package/dist/nips/nip17.d.mts +2 -0
- package/dist/nips/nip17.mjs +138 -0
- package/dist/nips/nip17.mjs.map +1 -0
- package/dist/nips/nip19.d.mts +2 -0
- package/dist/nips/nip19.mjs +174 -0
- package/dist/nips/nip19.mjs.map +1 -0
- package/dist/nips/nip21.d.mts +17 -0
- package/dist/nips/nip21.mjs +29 -0
- package/dist/nips/nip21.mjs.map +1 -0
- package/dist/nips/nip27.d.mts +42 -0
- package/dist/nips/nip27.mjs +175 -0
- package/dist/nips/nip27.mjs.map +1 -0
- package/dist/nips/nip42.d.mts +12 -0
- package/dist/nips/nip42.mjs +22 -0
- package/dist/nips/nip42.mjs.map +1 -0
- package/dist/nips/nip44.d.mts +12 -0
- package/dist/nips/nip44.mjs +125 -0
- package/dist/nips/nip44.mjs.map +1 -0
- package/dist/nips/nip46.d.mts +2 -0
- package/dist/nips/nip46.mjs +132 -0
- package/dist/nips/nip46.mjs.map +1 -0
- package/dist/nips/nip49.d.mts +12 -0
- package/dist/nips/nip49.mjs +80 -0
- package/dist/nips/nip49.mjs.map +1 -0
- package/dist/nips/nip51.d.mts +88 -0
- package/dist/nips/nip51.mjs +246 -0
- package/dist/nips/nip51.mjs.map +1 -0
- package/dist/nips/nip57.d.mts +43 -0
- package/dist/nips/nip57.mjs +230 -0
- package/dist/nips/nip57.mjs.map +1 -0
- package/dist/nips/nip59.d.mts +2 -0
- package/dist/nips/nip59.mjs +171 -0
- package/dist/nips/nip59.mjs.map +1 -0
- package/dist/nips/nip65.d.mts +2 -0
- package/dist/nips/nip65.mjs +76 -0
- package/dist/nips/nip65.mjs.map +1 -0
- package/dist/nips/nip77.d.mts +2 -0
- package/dist/nips/nip77.mjs +421 -0
- package/dist/nips/nip77.mjs.map +1 -0
- package/dist/nips/nip96.d.mts +31 -0
- package/dist/nips/nip96.mjs +95 -0
- package/dist/nips/nip96.mjs.map +1 -0
- package/dist/nips/nip98.d.mts +28 -0
- package/dist/nips/nip98.mjs +91 -0
- package/dist/nips/nip98.mjs.map +1 -0
- package/dist/nostr_crypto_wasm_bg-hi5aXi5P.mjs +4 -0
- package/dist/nostr_crypto_wasm_bg.wasm +0 -0
- package/dist/pool-C_j1OmBB.mjs +1881 -0
- package/dist/pool-C_j1OmBB.mjs.map +1 -0
- package/dist/pool-DhRi7l-b.d.mts +297 -0
- package/dist/relay.d.mts +4 -0
- package/dist/relay.mjs +2 -0
- package/dist/signer-CZ6K6FUc.mjs +473 -0
- package/dist/signer-CZ6K6FUc.mjs.map +1 -0
- package/dist/signer.d.mts +3 -0
- package/dist/signer.mjs +2 -0
- package/dist/storage-DOy0iLRE.mjs +870 -0
- package/dist/storage-DOy0iLRE.mjs.map +1 -0
- package/dist/storage.d.mts +4 -0
- package/dist/storage.mjs +4 -0
- package/dist/types-BzyADqnw.d.mts +19 -0
- package/dist/types-Dn24TPFs.d.mts +43 -0
- package/dist/util-6tfLQ9JF.mjs +73 -0
- package/dist/util-6tfLQ9JF.mjs.map +1 -0
- package/dist/wasm.d.mts +19 -0
- package/dist/wasm.mjs +237 -0
- package/dist/wasm.mjs.map +1 -0
- package/package.json +204 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
import { s as normalizeURL } from "./util-6tfLQ9JF.mjs";
|
|
2
|
+
import { a as NostrError } from "./error-DZEs5OuZ.mjs";
|
|
3
|
+
import { t as Kind } from "./kind-BLl4cLOc.mjs";
|
|
4
|
+
import { d as sortedEvents } from "./event-CzoQEFMF.mjs";
|
|
5
|
+
import { t as EventBuilder } from "./builder-q0VsvnYE.mjs";
|
|
6
|
+
import { n as canonicalizeFilters, t as canonicalizeFilter } from "./filter-BxwjEkZp.mjs";
|
|
7
|
+
import { n as fanIn, r as fetchRouted, t as Pool } from "./pool-C_j1OmBB.mjs";
|
|
8
|
+
import { storageFromItems } from "./nips/nip77.mjs";
|
|
9
|
+
import { isGiftWrapKind, requireNip59Crypto, unwrap } from "./nips/nip59.mjs";
|
|
10
|
+
import { Nip17Error, buildChatMessageRumor, dmRelayListEventBuilder, normalizeRecipients, requireDmRelays, wrapDirectMessage } from "./nips/nip17.mjs";
|
|
11
|
+
import { t as Gossip } from "./gossip-3xkRRCm_.mjs";
|
|
12
|
+
import { i as createOutboxFeed, t as createLoaders } from "./loaders-BNQszDAn.mjs";
|
|
13
|
+
import { n as toStorageError } from "./error-CwBxle6N.mjs";
|
|
14
|
+
import { t as MemoryEventStore } from "./memory-BCmhZPek.mjs";
|
|
15
|
+
//#region src/client/builder.ts
|
|
16
|
+
/** Fluent constructor for {@link Client}. */
|
|
17
|
+
var ClientBuilder = class {
|
|
18
|
+
#opts = {};
|
|
19
|
+
signer(signer) {
|
|
20
|
+
this.#opts = {
|
|
21
|
+
...this.#opts,
|
|
22
|
+
signer
|
|
23
|
+
};
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
relays(urls) {
|
|
27
|
+
this.#opts = {
|
|
28
|
+
...this.#opts,
|
|
29
|
+
relays: [...urls]
|
|
30
|
+
};
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
addRelay(url) {
|
|
34
|
+
this.#opts = {
|
|
35
|
+
...this.#opts,
|
|
36
|
+
relays: [...this.#opts.relays ?? [], url]
|
|
37
|
+
};
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
websocketImplementation(impl) {
|
|
41
|
+
this.#opts = {
|
|
42
|
+
...this.#opts,
|
|
43
|
+
websocketImplementation: impl
|
|
44
|
+
};
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
verifyEvent(fn) {
|
|
48
|
+
this.#opts = {
|
|
49
|
+
...this.#opts,
|
|
50
|
+
verifyEvent: fn
|
|
51
|
+
};
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
connectTimeoutMs(ms) {
|
|
55
|
+
this.#opts = {
|
|
56
|
+
...this.#opts,
|
|
57
|
+
connectTimeoutMs: ms
|
|
58
|
+
};
|
|
59
|
+
return this;
|
|
60
|
+
}
|
|
61
|
+
publishTimeoutMs(ms) {
|
|
62
|
+
this.#opts = {
|
|
63
|
+
...this.#opts,
|
|
64
|
+
publishTimeoutMs: ms
|
|
65
|
+
};
|
|
66
|
+
return this;
|
|
67
|
+
}
|
|
68
|
+
automaticAuth(enabled) {
|
|
69
|
+
this.#opts = {
|
|
70
|
+
...this.#opts,
|
|
71
|
+
automaticAuth: enabled
|
|
72
|
+
};
|
|
73
|
+
return this;
|
|
74
|
+
}
|
|
75
|
+
enableReconnect(enabled) {
|
|
76
|
+
this.#opts = {
|
|
77
|
+
...this.#opts,
|
|
78
|
+
enableReconnect: enabled
|
|
79
|
+
};
|
|
80
|
+
return this;
|
|
81
|
+
}
|
|
82
|
+
enablePing(enabled) {
|
|
83
|
+
this.#opts = {
|
|
84
|
+
...this.#opts,
|
|
85
|
+
enablePing: enabled
|
|
86
|
+
};
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
pingIntervalMs(ms) {
|
|
90
|
+
this.#opts = {
|
|
91
|
+
...this.#opts,
|
|
92
|
+
pingIntervalMs: ms
|
|
93
|
+
};
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
pingTimeoutMs(ms) {
|
|
97
|
+
this.#opts = {
|
|
98
|
+
...this.#opts,
|
|
99
|
+
pingTimeoutMs: ms
|
|
100
|
+
};
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Local event store. Defaults to {@link MemoryEventStore}.
|
|
105
|
+
* Browser apps that want persistence must pass {@link IndexedDbEventStore} and `await open()`.
|
|
106
|
+
*/
|
|
107
|
+
storage(store) {
|
|
108
|
+
this.#opts = {
|
|
109
|
+
...this.#opts,
|
|
110
|
+
storage: store
|
|
111
|
+
};
|
|
112
|
+
return this;
|
|
113
|
+
}
|
|
114
|
+
/** When false, Client will not auto-write events to storage. Default true. */
|
|
115
|
+
persistEvents(enabled) {
|
|
116
|
+
this.#opts = {
|
|
117
|
+
...this.#opts,
|
|
118
|
+
persistEvents: enabled
|
|
119
|
+
};
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
gossip(gossip) {
|
|
123
|
+
this.#opts = {
|
|
124
|
+
...this.#opts,
|
|
125
|
+
gossip
|
|
126
|
+
};
|
|
127
|
+
return this;
|
|
128
|
+
}
|
|
129
|
+
onstorageerror(fn) {
|
|
130
|
+
this.#opts = {
|
|
131
|
+
...this.#opts,
|
|
132
|
+
onstorageerror: fn
|
|
133
|
+
};
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
build() {
|
|
137
|
+
return new Client(this.#opts);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
//#endregion
|
|
141
|
+
//#region src/client/dm.ts
|
|
142
|
+
function giftWrapRelays(gossip, event) {
|
|
143
|
+
const targets = [];
|
|
144
|
+
for (const tag of event.tags) if (tag[0] === "p" && tag[1]) targets.push(tag[1].toLowerCase());
|
|
145
|
+
if (targets.length === 0) throw new Nip17Error("gift wrap has no p-tag recipient");
|
|
146
|
+
if (targets.length !== 1) throw new Nip17Error("gift wrap must have exactly one p-tag recipient");
|
|
147
|
+
const relays = gossip.dmRelays(targets[0]);
|
|
148
|
+
if (relays.length === 0) throw new Nip17Error("no kind 10050 in gossip; use sendPrivateMessage or pass relays");
|
|
149
|
+
return relays;
|
|
150
|
+
}
|
|
151
|
+
async function setDmRelays(deps, relays, opts) {
|
|
152
|
+
return deps.publish(dmRelayListEventBuilder(relays), {
|
|
153
|
+
gossip: true,
|
|
154
|
+
...opts
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async function sendPrivateMessage(deps, recipients, content, opts) {
|
|
158
|
+
deps.assertAlive();
|
|
159
|
+
const crypto = deps.requireNip59Crypto();
|
|
160
|
+
const sender = await crypto.getPublicKey();
|
|
161
|
+
const list = normalizeRecipients(recipients);
|
|
162
|
+
if (list.length === 0) throw new Nip17Error("recipients must not be empty");
|
|
163
|
+
const targets = /* @__PURE__ */ new Set([sender.toLowerCase(), ...list.map((r) => r.pubkey)]);
|
|
164
|
+
await deps.hydrateGossip([...targets]);
|
|
165
|
+
for (const pk of targets) requireDmRelays(pk, deps.gossip.dmRelays(pk));
|
|
166
|
+
const rumor = buildChatMessageRumor(sender, list, content, {
|
|
167
|
+
created_at: opts?.created_at,
|
|
168
|
+
subject: opts?.subject,
|
|
169
|
+
replyTo: opts?.replyTo
|
|
170
|
+
});
|
|
171
|
+
const wraps = await wrapDirectMessage(crypto, list, rumor);
|
|
172
|
+
return {
|
|
173
|
+
rumor,
|
|
174
|
+
wraps: await Promise.all(wraps.map(async ({ recipient, wrap }) => {
|
|
175
|
+
const relays = requireDmRelays(recipient, deps.gossip.dmRelays(recipient));
|
|
176
|
+
const results = await deps.pool.publish(relays, wrap, { timeoutMs: opts?.timeoutMs });
|
|
177
|
+
if (results.some((r) => r.result?.ok) && deps.wantObserve(opts?.observe)) deps.observe(wrap);
|
|
178
|
+
return {
|
|
179
|
+
recipient,
|
|
180
|
+
wrap,
|
|
181
|
+
results
|
|
182
|
+
};
|
|
183
|
+
}))
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async function fetchPrivateMessages(deps, opts) {
|
|
187
|
+
deps.assertAlive();
|
|
188
|
+
const crypto = deps.requireNip59Crypto();
|
|
189
|
+
const self = await crypto.getPublicKey();
|
|
190
|
+
deps.throwIfAborted(opts?.signal);
|
|
191
|
+
await deps.hydrateGossip([self]);
|
|
192
|
+
deps.throwIfAborted(opts?.signal);
|
|
193
|
+
const relays = requireDmRelays(self, deps.gossip.dmRelays(self));
|
|
194
|
+
const events = await deps.pool.fetch(relays, [{
|
|
195
|
+
kinds: [Kind.GiftWrap],
|
|
196
|
+
"#p": [self],
|
|
197
|
+
since: opts?.since,
|
|
198
|
+
until: opts?.until
|
|
199
|
+
}], {
|
|
200
|
+
timeoutMs: opts?.timeoutMs,
|
|
201
|
+
signal: opts?.signal
|
|
202
|
+
});
|
|
203
|
+
const byRumor = /* @__PURE__ */ new Map();
|
|
204
|
+
for (const wrap of events) try {
|
|
205
|
+
const rumor = await unwrap(crypto, wrap);
|
|
206
|
+
if (deps.wantObserve(opts?.observe)) deps.observe(wrap);
|
|
207
|
+
byRumor.set(rumor.id, {
|
|
208
|
+
wrap,
|
|
209
|
+
rumor
|
|
210
|
+
});
|
|
211
|
+
} 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
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async function subscribePrivateMessages(deps, opts) {
|
|
218
|
+
deps.assertAlive();
|
|
219
|
+
const crypto = deps.requireNip59Crypto();
|
|
220
|
+
const self = await crypto.getPublicKey();
|
|
221
|
+
deps.throwIfAborted(opts?.signal);
|
|
222
|
+
await deps.hydrateGossip([self]);
|
|
223
|
+
deps.throwIfAborted(opts?.signal);
|
|
224
|
+
const relays = requireDmRelays(self, deps.gossip.dmRelays(self));
|
|
225
|
+
const seen = /* @__PURE__ */ new Set();
|
|
226
|
+
let tail = Promise.resolve();
|
|
227
|
+
let closed = false;
|
|
228
|
+
const markClosed = () => {
|
|
229
|
+
closed = true;
|
|
230
|
+
};
|
|
231
|
+
if (opts?.signal?.aborted) markClosed();
|
|
232
|
+
else opts?.signal?.addEventListener("abort", markClosed, { once: true });
|
|
233
|
+
const inner = deps.pool.subscribe(relays, [{
|
|
234
|
+
kinds: [Kind.GiftWrap, Kind.GiftWrapEphemeral],
|
|
235
|
+
"#p": [self],
|
|
236
|
+
since: opts?.since
|
|
237
|
+
}], {
|
|
238
|
+
signal: opts?.signal,
|
|
239
|
+
oneose: opts?.oneose,
|
|
240
|
+
onclose: (reason) => {
|
|
241
|
+
markClosed();
|
|
242
|
+
opts?.onclose?.(reason);
|
|
243
|
+
},
|
|
244
|
+
eoseTimeoutMs: opts?.eoseTimeoutMs,
|
|
245
|
+
onevent: (wrap) => {
|
|
246
|
+
if (closed) return;
|
|
247
|
+
tail = tail.then(async () => {
|
|
248
|
+
if (closed) return;
|
|
249
|
+
try {
|
|
250
|
+
const rumor = await unwrap(crypto, wrap);
|
|
251
|
+
if (closed) return;
|
|
252
|
+
if (seen.has(rumor.id)) return;
|
|
253
|
+
seen.add(rumor.id);
|
|
254
|
+
if (deps.wantObserve(opts?.observe)) deps.observe(wrap);
|
|
255
|
+
opts?.onevent?.({
|
|
256
|
+
wrap,
|
|
257
|
+
rumor
|
|
258
|
+
});
|
|
259
|
+
} catch {}
|
|
260
|
+
}).catch(() => {});
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
return { close: (reason) => {
|
|
264
|
+
markClosed();
|
|
265
|
+
inner.close(reason);
|
|
266
|
+
} };
|
|
267
|
+
}
|
|
268
|
+
//#endregion
|
|
269
|
+
//#region src/client/gossip-io.ts
|
|
270
|
+
/** Remainder is one job on defaults; throw before any REQ when defaults are empty. */
|
|
271
|
+
function jobsForFilters(gossip, filters, defaultRelays) {
|
|
272
|
+
const routed = filters.map((f) => gossip.route(f));
|
|
273
|
+
const defaults = routed.some((r) => r.remainder !== void 0) ? defaultRelays() : void 0;
|
|
274
|
+
const jobs = [];
|
|
275
|
+
for (const r of routed) {
|
|
276
|
+
for (const [url, sub] of r.perRelay) jobs.push({
|
|
277
|
+
urls: [url],
|
|
278
|
+
filters: [sub]
|
|
279
|
+
});
|
|
280
|
+
if (r.remainder) jobs.push({
|
|
281
|
+
urls: defaults,
|
|
282
|
+
filters: [r.remainder]
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
return jobs;
|
|
286
|
+
}
|
|
287
|
+
function fetchGossip(pool, gossip, filters, defaultRelays, opts) {
|
|
288
|
+
return fetchRouted(pool, jobsForFilters(gossip, filters, defaultRelays), {
|
|
289
|
+
timeoutMs: opts?.timeoutMs,
|
|
290
|
+
signal: opts?.signal
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
function subscribeGossip(pool, gossip, filters, defaultRelays, opts) {
|
|
294
|
+
const jobs = jobsForFilters(gossip, filters, defaultRelays);
|
|
295
|
+
if (jobs.length === 0) {
|
|
296
|
+
queueMicrotask(() => opts.oneose?.());
|
|
297
|
+
return { close: () => {} };
|
|
298
|
+
}
|
|
299
|
+
return fanIn(pool, jobs, opts);
|
|
300
|
+
}
|
|
301
|
+
//#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
|
+
//#region src/client/sync.ts
|
|
312
|
+
const SYNC_ID_BATCH = 100;
|
|
313
|
+
const SYNC_UPLOAD_CONCURRENCY = 8;
|
|
314
|
+
function uniqueIds(ids) {
|
|
315
|
+
return [...new Set(ids)];
|
|
316
|
+
}
|
|
317
|
+
function emptySummary() {
|
|
318
|
+
return {
|
|
319
|
+
local: [],
|
|
320
|
+
remote: [],
|
|
321
|
+
sent: [],
|
|
322
|
+
received: [],
|
|
323
|
+
sendFailures: {},
|
|
324
|
+
persistFailures: {}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function mergeSyncSummary(into, other) {
|
|
328
|
+
const sendFailures = {
|
|
329
|
+
...into.sendFailures,
|
|
330
|
+
...other.sendFailures
|
|
331
|
+
};
|
|
332
|
+
const persistFailures = {
|
|
333
|
+
...into.persistFailures,
|
|
334
|
+
...other.persistFailures
|
|
335
|
+
};
|
|
336
|
+
return {
|
|
337
|
+
local: uniqueIds([...into.local, ...other.local]),
|
|
338
|
+
remote: uniqueIds([...into.remote, ...other.remote]),
|
|
339
|
+
sent: uniqueIds([...into.sent, ...other.sent]),
|
|
340
|
+
received: uniqueIds([...into.received, ...other.received]),
|
|
341
|
+
sendFailures,
|
|
342
|
+
persistFailures
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* NIP-77 sync against one relay: reconcile, then optionally upload
|
|
347
|
+
* local-only events and/or download remote-only events.
|
|
348
|
+
* `observe: false` skips putMany and ingestMeta; received ids are still listed.
|
|
349
|
+
* `persistEvents: false` skips putMany, still ingestMeta when observe is on.
|
|
350
|
+
*/
|
|
351
|
+
async function syncToRelay(deps, url, filter, opts) {
|
|
352
|
+
deps.assertAlive();
|
|
353
|
+
deps.throwIfAborted(opts?.signal);
|
|
354
|
+
const direction = opts?.direction ?? SyncDirection.Down;
|
|
355
|
+
filter = canonicalizeFilter(filter);
|
|
356
|
+
const items = await deps.storage.negentropyItems(filter);
|
|
357
|
+
const storage = storageFromItems(items);
|
|
358
|
+
const { have, need } = await (await deps.pool.ensureRelay(url, { signal: opts?.signal })).negReconcile(filter, storage, {
|
|
359
|
+
timeoutMs: opts?.timeoutMs,
|
|
360
|
+
signal: opts?.signal
|
|
361
|
+
});
|
|
362
|
+
const summary = {
|
|
363
|
+
local: have,
|
|
364
|
+
remote: need,
|
|
365
|
+
sent: [],
|
|
366
|
+
received: [],
|
|
367
|
+
sendFailures: {},
|
|
368
|
+
persistFailures: {}
|
|
369
|
+
};
|
|
370
|
+
if (opts?.dryRun) return summary;
|
|
371
|
+
if (direction === SyncDirection.Up || direction === SyncDirection.Both) {
|
|
372
|
+
if (have.length > 0) {
|
|
373
|
+
const found = await deps.storage.query([{ ids: have }]);
|
|
374
|
+
const foundById = new Map(found.map((event) => [event.id, event]));
|
|
375
|
+
for (const id of have) if (!foundById.has(id)) summary.sendFailures[id] = "event not found in local store";
|
|
376
|
+
for (let i = 0; i < found.length; i += SYNC_UPLOAD_CONCURRENCY) {
|
|
377
|
+
const chunk = found.slice(i, i + SYNC_UPLOAD_CONCURRENCY);
|
|
378
|
+
await Promise.all(chunk.map(async (event) => {
|
|
379
|
+
try {
|
|
380
|
+
const results = await deps.pool.publish([url], event, { timeoutMs: opts?.timeoutMs });
|
|
381
|
+
if (results.some((r) => r.result?.ok)) summary.sent.push(event.id);
|
|
382
|
+
else summary.sendFailures[event.id] = results[0]?.error ?? results[0]?.result?.message ?? "publish failed";
|
|
383
|
+
} catch (error) {
|
|
384
|
+
summary.sendFailures[event.id] = error instanceof Error ? error.message : String(error);
|
|
385
|
+
}
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if ((direction === SyncDirection.Down || direction === SyncDirection.Both) && need.length > 0) {
|
|
391
|
+
const shouldObserve = deps.wantObserve(opts?.observe);
|
|
392
|
+
for (let i = 0; i < need.length; i += SYNC_ID_BATCH) {
|
|
393
|
+
const batch = need.slice(i, i + SYNC_ID_BATCH);
|
|
394
|
+
deps.throwIfAborted(opts?.signal);
|
|
395
|
+
const events = await deps.pool.fetch([url], [{ ids: batch }], {
|
|
396
|
+
timeoutMs: opts?.timeoutMs,
|
|
397
|
+
signal: opts?.signal
|
|
398
|
+
});
|
|
399
|
+
if (!shouldObserve) {
|
|
400
|
+
for (const event of events) summary.received.push(event.id);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
if (!deps.persistEvents) {
|
|
404
|
+
for (const event of events) {
|
|
405
|
+
deps.ingestMeta(event);
|
|
406
|
+
summary.received.push(event.id);
|
|
407
|
+
}
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
let results;
|
|
411
|
+
try {
|
|
412
|
+
results = await deps.storage.putMany(events);
|
|
413
|
+
} catch (err) {
|
|
414
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
415
|
+
for (const event of events) summary.persistFailures[event.id] = message;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
for (let j = 0; j < events.length; j++) {
|
|
419
|
+
const event = events[j];
|
|
420
|
+
if (results[j] === "rejected") continue;
|
|
421
|
+
deps.ingestMeta(event);
|
|
422
|
+
summary.received.push(event.id);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
return summary;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* NIP-77 sync against the given relays (or Client default relays).
|
|
430
|
+
* Independent sessions run in parallel. Fulfilled summaries are merged;
|
|
431
|
+
* if every relay rejects, throws the first rejection in URL order.
|
|
432
|
+
*/
|
|
433
|
+
async function sync(deps, filter, opts) {
|
|
434
|
+
deps.assertAlive();
|
|
435
|
+
const urls = deps.defaultRelays(opts?.relays ? [...opts.relays] : void 0);
|
|
436
|
+
const results = await Promise.allSettled(urls.map((url) => syncToRelay(deps, url, filter, opts)));
|
|
437
|
+
let merged = emptySummary();
|
|
438
|
+
let fulfilled = 0;
|
|
439
|
+
let firstRejection;
|
|
440
|
+
for (const result of results) if (result.status === "fulfilled") {
|
|
441
|
+
fulfilled += 1;
|
|
442
|
+
merged = mergeSyncSummary(merged, result.value);
|
|
443
|
+
} else firstRejection ??= result.reason;
|
|
444
|
+
if (urls.length > 0 && fulfilled === 0) throw firstRejection;
|
|
445
|
+
return merged;
|
|
446
|
+
}
|
|
447
|
+
//#endregion
|
|
448
|
+
//#region src/client/client.ts
|
|
449
|
+
/**
|
|
450
|
+
* Layer-5 facade: signer + default relays + pool + loaders + gossip + event store.
|
|
451
|
+
*/
|
|
452
|
+
var Client = class {
|
|
453
|
+
pool;
|
|
454
|
+
loaders;
|
|
455
|
+
gossip;
|
|
456
|
+
storage;
|
|
457
|
+
onstorageerror;
|
|
458
|
+
#signer;
|
|
459
|
+
#relays;
|
|
460
|
+
#shutdown = false;
|
|
461
|
+
#persistEvents;
|
|
462
|
+
#persistQueue = [];
|
|
463
|
+
#flushing;
|
|
464
|
+
constructor(opts = {}) {
|
|
465
|
+
this.#signer = opts.signer;
|
|
466
|
+
this.#relays = [...opts.relays ?? []];
|
|
467
|
+
this.gossip = opts.gossip ?? new Gossip();
|
|
468
|
+
this.storage = opts.storage ?? new MemoryEventStore();
|
|
469
|
+
this.#persistEvents = opts.persistEvents ?? true;
|
|
470
|
+
this.onstorageerror = opts.onstorageerror ?? null;
|
|
471
|
+
const autoAuth = opts.automaticAuth ?? Boolean(opts.signer);
|
|
472
|
+
this.pool = new Pool({
|
|
473
|
+
websocketImplementation: opts.websocketImplementation,
|
|
474
|
+
verifyEvent: opts.verifyEvent,
|
|
475
|
+
enablePing: opts.enablePing,
|
|
476
|
+
pingIntervalMs: opts.pingIntervalMs,
|
|
477
|
+
pingTimeoutMs: opts.pingTimeoutMs,
|
|
478
|
+
connectTimeoutMs: opts.connectTimeoutMs,
|
|
479
|
+
publishTimeoutMs: opts.publishTimeoutMs,
|
|
480
|
+
enableReconnect: opts.enableReconnect ?? true,
|
|
481
|
+
automaticallyAuth: autoAuth ? () => {
|
|
482
|
+
const signer = this.#signer;
|
|
483
|
+
if (!signer) return null;
|
|
484
|
+
return async (template) => {
|
|
485
|
+
const pk = await signer.getPublicKey();
|
|
486
|
+
return signer.signEvent({
|
|
487
|
+
...template,
|
|
488
|
+
pubkey: pk
|
|
489
|
+
});
|
|
490
|
+
};
|
|
491
|
+
} : void 0
|
|
492
|
+
});
|
|
493
|
+
this.loaders = createLoaders({
|
|
494
|
+
pool: this.pool,
|
|
495
|
+
relays: this.#relays
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
static builder() {
|
|
499
|
+
return new ClientBuilder();
|
|
500
|
+
}
|
|
501
|
+
get signer() {
|
|
502
|
+
return this.#signer;
|
|
503
|
+
}
|
|
504
|
+
get relays() {
|
|
505
|
+
return this.#relays;
|
|
506
|
+
}
|
|
507
|
+
get isShutdown() {
|
|
508
|
+
return this.#shutdown;
|
|
509
|
+
}
|
|
510
|
+
setSigner(signer) {
|
|
511
|
+
this.#signer = signer;
|
|
512
|
+
}
|
|
513
|
+
addRelay(url) {
|
|
514
|
+
if (!this.#relays.includes(url)) {
|
|
515
|
+
this.#relays.push(url);
|
|
516
|
+
this.loaders.context.addRelay(url);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
removeRelay(url) {
|
|
520
|
+
this.#relays = this.#relays.filter((r) => r !== url);
|
|
521
|
+
this.loaders.context.removeRelay(url);
|
|
522
|
+
this.pool.close([url]);
|
|
523
|
+
}
|
|
524
|
+
/** Connect all configured relays (best-effort; failures are ignored). */
|
|
525
|
+
async connect(opts) {
|
|
526
|
+
this.#assertAlive();
|
|
527
|
+
await Promise.allSettled(this.#relays.map((url) => this.pool.ensureRelay(url, { signal: opts?.signal })));
|
|
528
|
+
}
|
|
529
|
+
async shutdown() {
|
|
530
|
+
this.#shutdown = true;
|
|
531
|
+
while (this.#flushing) await this.#flushing;
|
|
532
|
+
this.pool.close();
|
|
533
|
+
this.loaders.context.cache.clear();
|
|
534
|
+
}
|
|
535
|
+
#assertAlive() {
|
|
536
|
+
if (this.#shutdown) throw new ClientError("client is shut down");
|
|
537
|
+
}
|
|
538
|
+
#defaultRelays(urls) {
|
|
539
|
+
const list = urls ?? this.#relays;
|
|
540
|
+
if (list.length === 0) throw new ClientError("no relays configured");
|
|
541
|
+
return list;
|
|
542
|
+
}
|
|
543
|
+
#wantObserve(flag) {
|
|
544
|
+
return flag !== false;
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Unified ingest pipeline: gossip + replaceable loader cache immediately;
|
|
548
|
+
* storage writes are coalesced into a single-flight `putMany`.
|
|
549
|
+
*/
|
|
550
|
+
observe(event) {
|
|
551
|
+
this.#ingestMeta(event);
|
|
552
|
+
if (!this.#persistEvents) return;
|
|
553
|
+
this.#persistQueue.push(event);
|
|
554
|
+
this.#armFlush();
|
|
555
|
+
}
|
|
556
|
+
/** Observe many events (deduped by id, order preserved) as one persist batch. */
|
|
557
|
+
observeAll(events) {
|
|
558
|
+
const seen = /* @__PURE__ */ new Set();
|
|
559
|
+
for (const event of events) {
|
|
560
|
+
if (seen.has(event.id)) continue;
|
|
561
|
+
seen.add(event.id);
|
|
562
|
+
this.#ingestMeta(event);
|
|
563
|
+
if (this.#persistEvents) this.#persistQueue.push(event);
|
|
564
|
+
}
|
|
565
|
+
if (this.#persistEvents && this.#persistQueue.length > 0) this.#armFlush();
|
|
566
|
+
}
|
|
567
|
+
#ingestMeta(event) {
|
|
568
|
+
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
|
+
}
|
|
571
|
+
#armFlush() {
|
|
572
|
+
if (this.#flushing) return;
|
|
573
|
+
this.#flushing = this.#flushLoop();
|
|
574
|
+
}
|
|
575
|
+
async #flushLoop() {
|
|
576
|
+
try {
|
|
577
|
+
await new Promise((resolve) => {
|
|
578
|
+
queueMicrotask(resolve);
|
|
579
|
+
});
|
|
580
|
+
while (this.#persistQueue.length > 0) {
|
|
581
|
+
const batch = this.#persistQueue.splice(0);
|
|
582
|
+
try {
|
|
583
|
+
await this.storage.putMany(batch);
|
|
584
|
+
} catch (err) {
|
|
585
|
+
this.onstorageerror?.(toStorageError(err));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
} finally {
|
|
589
|
+
this.#flushing = void 0;
|
|
590
|
+
if (this.#persistQueue.length > 0) this.#armFlush();
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Explicitly load kind:10002 and kind:10050 lists for pubkeys and feed gossip.
|
|
595
|
+
* Does not background-fetch unknown authors on every subscribe (predictable DX).
|
|
596
|
+
*/
|
|
597
|
+
async hydrateGossip(pubkeys, opts) {
|
|
598
|
+
this.#assertAlive();
|
|
599
|
+
await Promise.all(pubkeys.map(async (pk) => {
|
|
600
|
+
const style = opts?.force ? "force" : "default";
|
|
601
|
+
const [relayList, dmList] = await Promise.all([this.loaders.relayList(pk, {
|
|
602
|
+
hints: opts?.hints,
|
|
603
|
+
style
|
|
604
|
+
}), this.loaders.dmRelayList(pk, {
|
|
605
|
+
hints: opts?.hints,
|
|
606
|
+
style
|
|
607
|
+
})]);
|
|
608
|
+
if (relayList.event) this.observe(relayList.event);
|
|
609
|
+
if (dmList.event) this.observe(dmList.event);
|
|
610
|
+
}));
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Create an outbox-model feed for the given authors (NIP-65 write relays).
|
|
614
|
+
* Live events go through {@link observe}; sync persists via applySync.
|
|
615
|
+
*/
|
|
616
|
+
outbox(opts) {
|
|
617
|
+
this.#assertAlive();
|
|
618
|
+
return createOutboxFeed({
|
|
619
|
+
pool: this.pool,
|
|
620
|
+
gossip: this.gossip,
|
|
621
|
+
storage: this.storage,
|
|
622
|
+
discoveryRelays: this.#relays,
|
|
623
|
+
authors: opts.authors,
|
|
624
|
+
kinds: opts.kinds,
|
|
625
|
+
onEvent: opts.onEvent,
|
|
626
|
+
maxRelaysPerAuthor: opts.maxRelaysPerAuthor,
|
|
627
|
+
observe: (event) => this.observe(event),
|
|
628
|
+
applySync: async (events) => {
|
|
629
|
+
if (!this.#persistEvents) {
|
|
630
|
+
for (const event of events) this.#ingestMeta(event);
|
|
631
|
+
return [];
|
|
632
|
+
}
|
|
633
|
+
const results = await this.storage.putMany(events);
|
|
634
|
+
const applied = [];
|
|
635
|
+
for (let i = 0; i < events.length; i++) {
|
|
636
|
+
if (results[i] === "rejected" || results[i] === "ephemeral") continue;
|
|
637
|
+
this.#ingestMeta(events[i]);
|
|
638
|
+
applied.push(events[i]);
|
|
639
|
+
}
|
|
640
|
+
return applied;
|
|
641
|
+
},
|
|
642
|
+
hydrate: (pubkeys) => this.hydrateGossip(pubkeys)
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
async getPublicKey() {
|
|
646
|
+
if (!this.#signer) throw new ClientError("no signer configured");
|
|
647
|
+
return this.#signer.getPublicKey();
|
|
648
|
+
}
|
|
649
|
+
async signEvent(unsigned) {
|
|
650
|
+
if (!this.#signer) throw new ClientError("no signer configured");
|
|
651
|
+
return this.#signer.signEvent(unsigned);
|
|
652
|
+
}
|
|
653
|
+
async signEventBuilder(builder) {
|
|
654
|
+
if (!this.#signer) throw new ClientError("no signer configured");
|
|
655
|
+
return builder.sign(this.#signer);
|
|
656
|
+
}
|
|
657
|
+
async signTemplate(template) {
|
|
658
|
+
if (!this.#signer) throw new ClientError("no signer configured");
|
|
659
|
+
const pubkey = await this.#signer.getPublicKey();
|
|
660
|
+
return this.#signer.signEvent({
|
|
661
|
+
...template,
|
|
662
|
+
pubkey
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Sign (if builder) and publish to relays.
|
|
667
|
+
* With `gossip: true`, prefers the author's NIP-65 outbox relays, tagged `p`
|
|
668
|
+
* inboxes, and up to 5 `e`/`a` tag relay hints when known.
|
|
669
|
+
* On any successful OK, observes the event into storage/gossip.
|
|
670
|
+
*/
|
|
671
|
+
async publish(eventOrBuilder, opts) {
|
|
672
|
+
this.#assertAlive();
|
|
673
|
+
const event = eventOrBuilder instanceof EventBuilder ? await this.signEventBuilder(eventOrBuilder) : eventOrBuilder;
|
|
674
|
+
let relays = opts?.relays;
|
|
675
|
+
if (!relays && isGiftWrapKind(event.kind)) relays = giftWrapRelays(this.gossip, event);
|
|
676
|
+
else if (!relays && opts?.gossip) {
|
|
677
|
+
const urls = [];
|
|
678
|
+
const add = (list) => {
|
|
679
|
+
for (const url of list) if (!urls.includes(url)) urls.push(url);
|
|
680
|
+
};
|
|
681
|
+
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]));
|
|
683
|
+
let hintCount = 0;
|
|
684
|
+
for (const tag of event.tags) {
|
|
685
|
+
if (hintCount >= 5) break;
|
|
686
|
+
if (tag[0] !== "e" && tag[0] !== "a" || !tag[2]) continue;
|
|
687
|
+
try {
|
|
688
|
+
const url = normalizeURL(tag[2]);
|
|
689
|
+
if (!urls.includes(url)) {
|
|
690
|
+
urls.push(url);
|
|
691
|
+
hintCount += 1;
|
|
692
|
+
}
|
|
693
|
+
} catch {}
|
|
694
|
+
}
|
|
695
|
+
if (urls.length > 0) relays = urls;
|
|
696
|
+
}
|
|
697
|
+
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);
|
|
699
|
+
return results;
|
|
700
|
+
}
|
|
701
|
+
/**
|
|
702
|
+
* Fetch events. With `gossip: true`, routes filters by NIP-65 when possible.
|
|
703
|
+
* With `localFirst: true`, merges storage hits with network results.
|
|
704
|
+
*/
|
|
705
|
+
async fetchEvents(filter, opts) {
|
|
706
|
+
this.#assertAlive();
|
|
707
|
+
const filters = canonicalizeFilters(Array.isArray(filter) ? filter : [filter]);
|
|
708
|
+
const shouldObserve = this.#wantObserve(opts?.observe);
|
|
709
|
+
const byId = /* @__PURE__ */ new Map();
|
|
710
|
+
if (opts?.localFirst) try {
|
|
711
|
+
const local = await this.storage.query(filters);
|
|
712
|
+
for (const e of local) byId.set(e.id, e);
|
|
713
|
+
} catch (err) {
|
|
714
|
+
this.onstorageerror?.(toStorageError(err));
|
|
715
|
+
}
|
|
716
|
+
const ingest = (events) => {
|
|
717
|
+
for (const e of events) {
|
|
718
|
+
byId.set(e.id, e);
|
|
719
|
+
if (shouldObserve) this.observe(e);
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
if (!opts?.gossip || opts.relays) {
|
|
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()]);
|
|
728
|
+
}
|
|
729
|
+
ingest(await fetchGossip(this.pool, this.gossip, filters, () => this.#defaultRelays(), {
|
|
730
|
+
timeoutMs: opts?.timeoutMs,
|
|
731
|
+
signal: opts?.signal
|
|
732
|
+
}));
|
|
733
|
+
return sortedEvents([...byId.values()]);
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Query local storage only (no network).
|
|
737
|
+
*/
|
|
738
|
+
async queryLocal(filter) {
|
|
739
|
+
this.#assertAlive();
|
|
740
|
+
const filters = canonicalizeFilters(Array.isArray(filter) ? filter : [filter]);
|
|
741
|
+
return this.storage.query(filters);
|
|
742
|
+
}
|
|
743
|
+
subscribe(filter, opts) {
|
|
744
|
+
this.#assertAlive();
|
|
745
|
+
const filters = canonicalizeFilters(Array.isArray(filter) ? filter : [filter]);
|
|
746
|
+
const shouldObserve = this.#wantObserve(opts?.observe);
|
|
747
|
+
const wrapEvent = (event) => {
|
|
748
|
+
if (shouldObserve) this.observe(event);
|
|
749
|
+
opts?.onevent?.(event);
|
|
750
|
+
};
|
|
751
|
+
if (!opts?.gossip || opts.relays) return this.pool.subscribe(this.#defaultRelays(opts?.relays), filters, {
|
|
752
|
+
onevent: wrapEvent,
|
|
753
|
+
oneose: opts?.oneose,
|
|
754
|
+
onclose: opts?.onclose,
|
|
755
|
+
signal: opts?.signal,
|
|
756
|
+
id: opts?.id,
|
|
757
|
+
eoseTimeoutMs: opts?.eoseTimeoutMs
|
|
758
|
+
});
|
|
759
|
+
return subscribeGossip(this.pool, this.gossip, filters, () => this.#defaultRelays(), {
|
|
760
|
+
onevent: wrapEvent,
|
|
761
|
+
oneose: opts?.oneose,
|
|
762
|
+
onclose: opts?.onclose,
|
|
763
|
+
signal: opts?.signal,
|
|
764
|
+
eoseTimeoutMs: opts?.eoseTimeoutMs
|
|
765
|
+
});
|
|
766
|
+
}
|
|
767
|
+
#requireNip59Crypto() {
|
|
768
|
+
if (!this.#signer) throw new ClientError("no signer configured");
|
|
769
|
+
return requireNip59Crypto(this.#signer);
|
|
770
|
+
}
|
|
771
|
+
#throwIfAborted(signal) {
|
|
772
|
+
if (!signal?.aborted) return;
|
|
773
|
+
if (signal.reason instanceof Error) throw signal.reason;
|
|
774
|
+
throw new ClientError("aborted");
|
|
775
|
+
}
|
|
776
|
+
#dmDeps() {
|
|
777
|
+
return {
|
|
778
|
+
pool: this.pool,
|
|
779
|
+
gossip: this.gossip,
|
|
780
|
+
hydrateGossip: (pubkeys) => this.hydrateGossip(pubkeys),
|
|
781
|
+
observe: (event) => this.observe(event),
|
|
782
|
+
assertAlive: () => this.#assertAlive(),
|
|
783
|
+
requireNip59Crypto: () => this.#requireNip59Crypto(),
|
|
784
|
+
throwIfAborted: (signal) => this.#throwIfAborted(signal),
|
|
785
|
+
wantObserve: (flag) => this.#wantObserve(flag),
|
|
786
|
+
publish: (eventOrBuilder, opts) => this.publish(eventOrBuilder, opts)
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
#syncDeps() {
|
|
790
|
+
return {
|
|
791
|
+
pool: this.pool,
|
|
792
|
+
storage: this.storage,
|
|
793
|
+
persistEvents: this.#persistEvents,
|
|
794
|
+
assertAlive: () => this.#assertAlive(),
|
|
795
|
+
throwIfAborted: (signal) => this.#throwIfAborted(signal),
|
|
796
|
+
wantObserve: (flag) => this.#wantObserve(flag),
|
|
797
|
+
ingestMeta: (event) => this.#ingestMeta(event),
|
|
798
|
+
defaultRelays: (urls) => this.#defaultRelays(urls)
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
/** Publish a kind:10050 NIP-17 DM relay list (`relay` tags). Not kind 10002. */
|
|
802
|
+
async setDmRelays(relays, opts) {
|
|
803
|
+
return setDmRelays(this.#dmDeps(), relays, opts);
|
|
804
|
+
}
|
|
805
|
+
async sendPrivateMessage(recipients, content, opts) {
|
|
806
|
+
return sendPrivateMessage(this.#dmDeps(), recipients, content, opts);
|
|
807
|
+
}
|
|
808
|
+
async fetchPrivateMessages(opts) {
|
|
809
|
+
return fetchPrivateMessages(this.#dmDeps(), opts);
|
|
810
|
+
}
|
|
811
|
+
async subscribePrivateMessages(opts) {
|
|
812
|
+
return subscribePrivateMessages(this.#dmDeps(), opts);
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* NIP-77 sync against one relay: reconcile, then optionally upload
|
|
816
|
+
* local-only events and/or download remote-only events.
|
|
817
|
+
* `observe: false` skips putMany and ingestMeta; received ids are still listed.
|
|
818
|
+
* `persistEvents: false` skips putMany, still ingestMeta when observe is on.
|
|
819
|
+
*/
|
|
820
|
+
async syncToRelay(url, filter, opts) {
|
|
821
|
+
return syncToRelay(this.#syncDeps(), url, filter, opts);
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* NIP-77 sync against the given relays (or Client default relays).
|
|
825
|
+
* Independent sessions run in parallel. Fulfilled summaries are merged;
|
|
826
|
+
* if every relay rejects, throws the first rejection in URL order.
|
|
827
|
+
*/
|
|
828
|
+
async sync(filter, opts) {
|
|
829
|
+
return sync(this.#syncDeps(), filter, opts);
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
//#endregion
|
|
833
|
+
export { ClientBuilder as i, ClientError as n, SyncDirection as r, Client as t };
|
|
834
|
+
|
|
835
|
+
//# sourceMappingURL=client-Bd8B1mYm.mjs.map
|