@qntx/nostr 0.5.0 → 0.7.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 → abort-DBq2STPj.mjs} +13 -6
- package/dist/abort-DBq2STPj.mjs.map +1 -0
- package/dist/{builder-9tjI6WQ3.mjs → builder-CdLE4J6O.mjs} +11 -17
- package/dist/builder-CdLE4J6O.mjs.map +1 -0
- package/dist/{builder-sSflu6nB.d.mts → builder-DgkqDylK.d.mts} +5 -6
- package/dist/{client-BtIAtxrL.mjs → client-CILhdfsF.mjs} +104 -314
- package/dist/client-CILhdfsF.mjs.map +1 -0
- package/dist/client.d.mts +2 -2
- package/dist/client.mjs +2 -2
- package/dist/collections-DtwMc1NU.mjs +33 -0
- package/dist/collections-DtwMc1NU.mjs.map +1 -0
- package/dist/core.d.mts +9 -8
- package/dist/core.mjs +7 -7
- package/dist/{error-BW7re1UN.d.mts → error-BD_0QFKT.d.mts} +2 -2
- package/dist/{error-CR8Urc84.d.mts → error-BquXDmIN.d.mts} +4 -4
- package/dist/{error-DrEIM7FO.mjs → error-L_QdmSnV.mjs} +2 -2
- package/dist/{error-DrEIM7FO.mjs.map → error-L_QdmSnV.mjs.map} +1 -1
- package/dist/{error-B__OMBRY.mjs → error-gtJvV6YS.mjs} +2 -2
- package/dist/{error-B__OMBRY.mjs.map → error-gtJvV6YS.mjs.map} +1 -1
- package/dist/{event-fU3Y01PR.d.mts → event-DJeb_DIL.d.mts} +4 -2
- package/dist/{event-D8Nfoh4K.mjs → event-DinQMmJ1.mjs} +16 -10
- package/dist/event-DinQMmJ1.mjs.map +1 -0
- package/dist/{filter-C0IMByE3.d.mts → filter-jI_9htF7.d.mts} +2 -2
- package/dist/{gossip-DpIvkBuj.d.mts → gossip-C7HLW5kI.d.mts} +35 -26
- package/dist/{gossip-oloDMWEj.mjs → gossip-CcTp9VpU.mjs} +23 -33
- package/dist/gossip-CcTp9VpU.mjs.map +1 -0
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/{index-CvkvqB16.d.mts → index-DU9tRFTZ.d.mts} +7 -20
- package/dist/{index-q4MDce_r.d.mts → index-DiXwNn2M.d.mts} +6 -8
- package/dist/{index-QxvrpMfF.d.mts → index-DocgD9K0.d.mts} +92 -167
- package/dist/{index-D1bqFHKB.d.mts → index-InQZpHyQ.d.mts} +18 -18
- package/dist/{index-MxC28S4P.d.mts → index-Tx15HGAD.d.mts} +26 -28
- package/dist/{index-Dmc8FJgO.d.mts → index-iLT8UzY3.d.mts} +19 -37
- package/dist/index.d.mts +23 -21
- package/dist/index.mjs +21 -19
- package/dist/instance-BcKzxHIP.mjs +220 -0
- package/dist/instance-BcKzxHIP.mjs.map +1 -0
- package/dist/instance-DjsL8h84.d.mts +15 -0
- package/dist/{key-Cv4_pN3x.d.mts → key-D_5OgGT7.d.mts} +11 -7
- package/dist/{key-Bps-LqX7.mjs → key-bshqfmZL.mjs} +13 -9
- package/dist/key-bshqfmZL.mjs.map +1 -0
- package/dist/{loaders-o6YXB_Ri.mjs → loaders-DD0XAGzw.mjs} +73 -82
- package/dist/loaders-DD0XAGzw.mjs.map +1 -0
- package/dist/loaders.d.mts +3 -3
- package/dist/loaders.mjs +2 -2
- package/dist/{memory-C2EVx0_O.mjs → memory-D_-5-T31.mjs} +2 -2
- package/dist/memory-D_-5-T31.mjs.map +1 -0
- package/dist/{memory-index-Do1mz0WF.mjs → memory-index-C6rujtoc.mjs} +30 -247
- package/dist/memory-index-C6rujtoc.mjs.map +1 -0
- package/dist/message-CWLcFTQ1.mjs +194 -0
- package/dist/message-CWLcFTQ1.mjs.map +1 -0
- package/dist/{message-iGPIqnn0.d.mts → message-CbBZOhq3.d.mts} +3 -3
- package/dist/{nip17-CKIufV98.d.mts → nip17-cTDFfFVt.d.mts} +5 -6
- package/dist/{nip19-BevCJZsp.d.mts → nip19-D1OnJ192.d.mts} +3 -9
- package/dist/{nip46-DDV4dC56.d.mts → nip46-DrsPI8EX.d.mts} +3 -5
- package/dist/nip51-DoPUvQ--.d.mts +87 -0
- package/dist/{nip59-zQgdLcEr.d.mts → nip59-BFIihLYY.d.mts} +3 -4
- package/dist/nip65-52HiwiyD.d.mts +23 -0
- package/dist/{nip77-DH4RhS5r.d.mts → nip77-C9QcZn7b.d.mts} +3 -4
- package/dist/nips/blossom.d.mts +12 -8
- package/dist/nips/blossom.mjs +30 -9
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.mjs +17 -7
- package/dist/nips/nip04.mjs.map +1 -1
- package/dist/nips/nip05.d.mts +2 -3
- package/dist/nips/nip05.mjs +1 -4
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +3 -3
- package/dist/nips/nip10.mjs +3 -3
- package/dist/nips/nip11.d.mts +1 -1
- package/dist/nips/nip11.mjs +4 -4
- package/dist/nips/nip11.mjs.map +1 -1
- package/dist/nips/nip13.d.mts +7 -7
- package/dist/nips/nip13.mjs +32 -10
- package/dist/nips/nip13.mjs.map +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +13 -33
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +2 -2
- package/dist/nips/nip19.mjs +16 -28
- package/dist/nips/nip19.mjs.map +1 -1
- package/dist/nips/nip21.d.mts +6 -1
- package/dist/nips/nip21.mjs +15 -5
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +4 -4
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.d.mts +1 -1
- package/dist/nips/nip42.mjs +2 -1
- package/dist/nips/nip42.mjs.map +1 -1
- package/dist/nips/nip44.mjs +1 -1
- package/dist/nips/nip46.d.mts +2 -2
- package/dist/nips/nip46.mjs +15 -19
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +5 -5
- package/dist/nips/nip49.mjs +4 -4
- package/dist/nips/nip49.mjs.map +1 -1
- package/dist/nips/nip51.d.mts +2 -86
- package/dist/nips/nip51.mjs +7 -23
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.d.mts +7 -5
- package/dist/nips/nip57.mjs +12 -17
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +16 -16
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +2 -2
- package/dist/nips/nip65.mjs +14 -37
- package/dist/nips/nip65.mjs.map +1 -1
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +33 -32
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +5 -5
- package/dist/nips/nip98.mjs +8 -8
- package/dist/nips/nip98.mjs.map +1 -1
- package/dist/nostr_crypto_wasm_bg.wasm +0 -0
- package/dist/{pool-DBIYMtwy.d.mts → pool-SzJKPIa3.d.mts} +96 -91
- package/dist/pool-YHRZgcQ3.mjs +596 -0
- package/dist/pool-YHRZgcQ3.mjs.map +1 -0
- package/dist/put-DS0yzVJx.mjs +187 -0
- package/dist/put-DS0yzVJx.mjs.map +1 -0
- package/dist/{reactive-DFpqtIf_.d.mts → reactive-BJ1ggn4d.d.mts} +9 -22
- package/dist/{reactive-DDlThDSZ.mjs → reactive-BJRoVHxU.mjs} +113 -64
- package/dist/reactive-BJRoVHxU.mjs.map +1 -0
- package/dist/{pool-As20OGxB.mjs → relay-CvxPiQ_m.mjs} +178 -721
- package/dist/relay-CvxPiQ_m.mjs.map +1 -0
- package/dist/relay.d.mts +4 -4
- package/dist/relay.mjs +3 -2
- package/dist/serial-Bzpw-xz3.mjs +21 -0
- package/dist/serial-Bzpw-xz3.mjs.map +1 -0
- package/dist/{signer-CYQutDzD.mjs → signer-yW3slDC2.mjs} +12 -14
- package/dist/signer-yW3slDC2.mjs.map +1 -0
- package/dist/signer.d.mts +2 -2
- package/dist/signer.mjs +2 -2
- package/dist/{storage-Dky3wuoH.mjs → storage-CZ52ifcQ.mjs} +17 -42
- package/dist/storage-CZ52ifcQ.mjs.map +1 -0
- package/dist/storage.d.mts +3 -3
- package/dist/storage.mjs +4 -4
- package/dist/store.d.mts +1 -1
- package/dist/store.mjs +1 -1
- package/dist/testing.d.mts +18 -18
- package/dist/testing.mjs +9 -9
- package/dist/testing.mjs.map +1 -1
- package/dist/{types-2pCUprRU.d.mts → types-Cm-XMgtH.d.mts} +6 -6
- package/dist/{types-CRQEk0Oi.d.mts → types-DNmiscfd.d.mts} +2 -2
- package/dist/{util-LfQirgDk.mjs → util-yFYzlbrm.mjs} +29 -7
- package/dist/util-yFYzlbrm.mjs.map +1 -0
- package/dist/{verifier-t5WoWbtY.mjs → verifier-BTLOpo1n.mjs} +3 -3
- package/dist/verifier-BTLOpo1n.mjs.map +1 -0
- package/dist/verifier-CgTFpgsC.d.mts +22 -0
- package/dist/wasm.browser.d.mts +8 -0
- package/dist/wasm.browser.mjs +19 -0
- package/dist/wasm.browser.mjs.map +1 -0
- package/dist/wasm.d.mts +3 -13
- package/dist/wasm.mjs +7 -208
- package/dist/wasm.mjs.map +1 -1
- package/package.json +13 -10
- package/dist/abort-Ba8tDrOI.mjs.map +0 -1
- package/dist/builder-9tjI6WQ3.mjs.map +0 -1
- package/dist/client-BtIAtxrL.mjs.map +0 -1
- package/dist/event-D8Nfoh4K.mjs.map +0 -1
- package/dist/gossip-oloDMWEj.mjs.map +0 -1
- package/dist/key-Bps-LqX7.mjs.map +0 -1
- package/dist/loaders-o6YXB_Ri.mjs.map +0 -1
- package/dist/memory-C2EVx0_O.mjs.map +0 -1
- package/dist/memory-index-Do1mz0WF.mjs.map +0 -1
- package/dist/message-DcY6Fi9o.mjs +0 -193
- package/dist/message-DcY6Fi9o.mjs.map +0 -1
- package/dist/nip65-CjgmdpPB.d.mts +0 -26
- package/dist/nips/nip96.d.mts +0 -40
- package/dist/nips/nip96.mjs +0 -118
- package/dist/nips/nip96.mjs.map +0 -1
- package/dist/pool-As20OGxB.mjs.map +0 -1
- package/dist/reactive-DDlThDSZ.mjs.map +0 -1
- package/dist/signer-CYQutDzD.mjs.map +0 -1
- package/dist/storage-Dky3wuoH.mjs.map +0 -1
- package/dist/util-LfQirgDk.mjs.map +0 -1
- package/dist/verifier-t5WoWbtY.mjs.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as verifyEvent } from "./verifier-
|
|
1
|
+
import { _ as MessageError, b as WasmPoisonedError, u as normalizeURL, v as NostrError } from "./util-yFYzlbrm.mjs";
|
|
2
|
+
import { n as verifyEvent } from "./verifier-BTLOpo1n.mjs";
|
|
3
3
|
import { n as canonicalizeFilters, r as filterFingerprint, t as canonicalizeFilter } from "./filter-BQl3GlHt.mjs";
|
|
4
|
-
import { n as createSubscriptionId, r as encodeClientMessage, s as parseRelayMessage, t as assertSubscriptionId } from "./message-
|
|
5
|
-
import { t as NoSignerError } from "./error-
|
|
4
|
+
import { n as createSubscriptionId, r as encodeClientMessage, s as parseRelayMessage, t as assertSubscriptionId } from "./message-CWLcFTQ1.mjs";
|
|
5
|
+
import { t as NoSignerError } from "./error-gtJvV6YS.mjs";
|
|
6
6
|
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
7
7
|
import { isAuthRequired, makeAuthEvent } from "./nips/nip42.mjs";
|
|
8
|
-
import { n as
|
|
8
|
+
import { i as throwIfAborted, n as onAbort, r as raceSignal, t as abortReason } from "./abort-DBq2STPj.mjs";
|
|
9
9
|
import { Nip77Error, runNegSession } from "./nips/nip77.mjs";
|
|
10
10
|
//#region src/relay/error.ts
|
|
11
11
|
/** Base class for relay errors; carries the relay URL when known. */
|
|
@@ -62,21 +62,6 @@ function getWebSocketImplementation() {
|
|
|
62
62
|
return impl;
|
|
63
63
|
}
|
|
64
64
|
//#endregion
|
|
65
|
-
//#region src/relay/url.ts
|
|
66
|
-
/**
|
|
67
|
-
* True for `ws:` / `http:` URLs that are not `.onion`. Local-network `ws://` is still insecure;
|
|
68
|
-
* allow those via a trusted set.
|
|
69
|
-
*/
|
|
70
|
-
function isInsecureRelayUrl(url) {
|
|
71
|
-
try {
|
|
72
|
-
const parsed = new URL(url);
|
|
73
|
-
if (parsed.hostname.endsWith(".onion")) return false;
|
|
74
|
-
return parsed.protocol === "ws:" || parsed.protocol === "http:";
|
|
75
|
-
} catch {
|
|
76
|
-
return false;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
//#endregion
|
|
80
65
|
//#region src/relay/neg-session.ts
|
|
81
66
|
function createNegSession() {
|
|
82
67
|
return {
|
|
@@ -117,25 +102,26 @@ async function runWiredNegSession(opts) {
|
|
|
117
102
|
return new Promise((resolve, reject) => {
|
|
118
103
|
const timer = setTimeout(() => {
|
|
119
104
|
session.waiter = void 0;
|
|
105
|
+
disposeAbort();
|
|
120
106
|
reject(timedOut());
|
|
121
107
|
}, remainingMs());
|
|
122
108
|
const fail = (err) => {
|
|
123
109
|
clearTimeout(timer);
|
|
124
110
|
session.waiter = void 0;
|
|
111
|
+
disposeAbort();
|
|
125
112
|
reject(err);
|
|
126
113
|
};
|
|
127
|
-
const onAbort = () => {
|
|
128
|
-
if (signal) fail(abortReason(signal));
|
|
129
|
-
};
|
|
130
114
|
session.waiter = {
|
|
131
115
|
resolve: (hex) => {
|
|
132
116
|
clearTimeout(timer);
|
|
133
|
-
|
|
117
|
+
disposeAbort();
|
|
134
118
|
resolve(hex);
|
|
135
119
|
},
|
|
136
120
|
reject: (err) => fail(err)
|
|
137
121
|
};
|
|
138
|
-
|
|
122
|
+
const disposeAbort = onAbort(signal, () => {
|
|
123
|
+
if (signal) fail(abortReason(signal));
|
|
124
|
+
});
|
|
139
125
|
});
|
|
140
126
|
};
|
|
141
127
|
return runNegSession({
|
|
@@ -310,17 +296,17 @@ var Subscription = class {
|
|
|
310
296
|
};
|
|
311
297
|
if (opts.signal) {
|
|
312
298
|
if (opts.signal.aborted) this.close("aborted");
|
|
313
|
-
else
|
|
314
|
-
const onAbort = () => this.close("aborted");
|
|
315
|
-
opts.signal.addEventListener("abort", onAbort, { once: true });
|
|
316
|
-
this.#abort = () => opts.signal?.removeEventListener("abort", onAbort);
|
|
317
|
-
}
|
|
299
|
+
else this.#abort = onAbort(opts.signal, () => this.close("aborted"));
|
|
318
300
|
}
|
|
319
301
|
}
|
|
302
|
+
/** Drop the signal listener. Terminal paths that mark `closed` without `close()` run this too. */
|
|
303
|
+
dispose() {
|
|
304
|
+
this.#abort?.();
|
|
305
|
+
}
|
|
320
306
|
close(reason = "closed by client") {
|
|
321
307
|
if (this.closed) return;
|
|
322
308
|
this.closed = true;
|
|
323
|
-
this
|
|
309
|
+
this.dispose();
|
|
324
310
|
this.#sendClose(this.id);
|
|
325
311
|
invokeSafely(() => this.handlers.onclose?.(reason));
|
|
326
312
|
}
|
|
@@ -355,6 +341,7 @@ function subscriptionToAsyncIterable(start, opts) {
|
|
|
355
341
|
let wake;
|
|
356
342
|
let closer;
|
|
357
343
|
let localClose = false;
|
|
344
|
+
let disposeAbort;
|
|
358
345
|
const notify = () => {
|
|
359
346
|
wake?.();
|
|
360
347
|
wake = void 0;
|
|
@@ -364,6 +351,7 @@ function subscriptionToAsyncIterable(start, opts) {
|
|
|
364
351
|
});
|
|
365
352
|
const closeLocal = (reason) => {
|
|
366
353
|
localClose = true;
|
|
354
|
+
disposeAbort?.();
|
|
367
355
|
closer?.close(reason);
|
|
368
356
|
done = true;
|
|
369
357
|
notify();
|
|
@@ -385,7 +373,10 @@ function subscriptionToAsyncIterable(start, opts) {
|
|
|
385
373
|
});
|
|
386
374
|
if (opts?.signal) {
|
|
387
375
|
if (opts.signal.aborted) closeLocal("aborted");
|
|
388
|
-
else
|
|
376
|
+
else {
|
|
377
|
+
disposeAbort = onAbort(opts.signal, () => closeLocal("aborted"));
|
|
378
|
+
if (done) disposeAbort();
|
|
379
|
+
}
|
|
389
380
|
}
|
|
390
381
|
return {
|
|
391
382
|
close(reason) {
|
|
@@ -421,6 +412,7 @@ function subscriptionToAsyncIterable(start, opts) {
|
|
|
421
412
|
}
|
|
422
413
|
//#endregion
|
|
423
414
|
//#region src/relay/subscribe.ts
|
|
415
|
+
/** REQ subscription runtime: exclusive one-shot, live coalescing, dispatch, reconnect replay. */
|
|
424
416
|
function openExclusive(ctx, filters, opts) {
|
|
425
417
|
const sub = new Subscription(filters, opts, (id) => {
|
|
426
418
|
ctx.subs.delete(id);
|
|
@@ -495,6 +487,7 @@ function forgetLiveGroup(ctx, group) {
|
|
|
495
487
|
ctx.liveBySubId.delete(group.sub.id);
|
|
496
488
|
ctx.subs.delete(group.sub.id);
|
|
497
489
|
group.sub.closed = true;
|
|
490
|
+
group.sub.dispose();
|
|
498
491
|
}
|
|
499
492
|
function detachLive(ctx, group, handle) {
|
|
500
493
|
if (!group.attachments.delete(handle)) return;
|
|
@@ -560,6 +553,7 @@ function closeAllSubscriptions(ctx, reason) {
|
|
|
560
553
|
});
|
|
561
554
|
for (const sub of ctx.subs.values()) if (!sub.closed) {
|
|
562
555
|
sub.closed = true;
|
|
556
|
+
sub.dispose();
|
|
563
557
|
invokeSafely(() => {
|
|
564
558
|
sub.handlers.onclose?.(reason);
|
|
565
559
|
});
|
|
@@ -579,6 +573,7 @@ function dropSubscription(ctx, sub, reason) {
|
|
|
579
573
|
}
|
|
580
574
|
ctx.subs.delete(sub.id);
|
|
581
575
|
sub.closed = true;
|
|
576
|
+
sub.dispose();
|
|
582
577
|
invokeSafely(() => {
|
|
583
578
|
sub.handlers.onclose?.(reason);
|
|
584
579
|
});
|
|
@@ -656,6 +651,7 @@ async function fetchFilters(subscribe, filters, opts) {
|
|
|
656
651
|
const done = (err) => {
|
|
657
652
|
if (settled) return;
|
|
658
653
|
settled = true;
|
|
654
|
+
disposeAbort();
|
|
659
655
|
clearTimeout(timer);
|
|
660
656
|
let reason = "aborted";
|
|
661
657
|
if (err instanceof Error) reason = err.message;
|
|
@@ -664,6 +660,9 @@ async function fetchFilters(subscribe, filters, opts) {
|
|
|
664
660
|
if (err === void 0) resolve();
|
|
665
661
|
else reject(err);
|
|
666
662
|
};
|
|
663
|
+
const disposeAbort = onAbort(opts.signal, () => {
|
|
664
|
+
if (opts.signal !== void 0) done(abortReason(opts.signal));
|
|
665
|
+
});
|
|
667
666
|
const timer = setTimeout(() => {
|
|
668
667
|
end = { type: "timeout" };
|
|
669
668
|
done();
|
|
@@ -694,7 +693,6 @@ async function fetchFilters(subscribe, filters, opts) {
|
|
|
694
693
|
done();
|
|
695
694
|
}
|
|
696
695
|
});
|
|
697
|
-
opts.signal?.addEventListener("abort", () => done(opts.signal ? abortReason(opts.signal) : void 0), { once: true });
|
|
698
696
|
});
|
|
699
697
|
return {
|
|
700
698
|
events,
|
|
@@ -722,7 +720,17 @@ function armEoseTimeout(sub, eoseTimeoutMs) {
|
|
|
722
720
|
}
|
|
723
721
|
//#endregion
|
|
724
722
|
//#region src/relay/relay.ts
|
|
725
|
-
/**
|
|
723
|
+
/**
|
|
724
|
+
* Relay lifecycle states:
|
|
725
|
+
*
|
|
726
|
+
* - `initialized` — constructed; `connect()` not yet attempted.
|
|
727
|
+
* - `connecting` — a `connect()` attempt is in flight (socket opening / resubscribing).
|
|
728
|
+
* - `connected` — socket open; REQ/EVENT/COUNT flow.
|
|
729
|
+
* - `disconnected` — socket down: a reconnect is scheduled when `enableReconnect` has live
|
|
730
|
+
* subscriptions to restore, or `disconnect()` severed manually (REQ state kept).
|
|
731
|
+
* - `closed` — terminal until the next `connect()`: `close()` or a socket death without a scheduled
|
|
732
|
+
* reconnect.
|
|
733
|
+
*/
|
|
726
734
|
const RelayStatus = {
|
|
727
735
|
Initialized: "initialized",
|
|
728
736
|
Connecting: "connecting",
|
|
@@ -730,6 +738,7 @@ const RelayStatus = {
|
|
|
730
738
|
Disconnected: "disconnected",
|
|
731
739
|
Closed: "closed"
|
|
732
740
|
};
|
|
741
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 4400;
|
|
733
742
|
const DEFAULT_BACKOFF = [
|
|
734
743
|
1e3,
|
|
735
744
|
2e3,
|
|
@@ -747,7 +756,6 @@ const noop = () => {};
|
|
|
747
756
|
var Relay = class Relay {
|
|
748
757
|
url;
|
|
749
758
|
#ws;
|
|
750
|
-
#connected = false;
|
|
751
759
|
#gen = 0;
|
|
752
760
|
#status = RelayStatus.Initialized;
|
|
753
761
|
#connecting;
|
|
@@ -777,25 +785,19 @@ var Relay = class Relay {
|
|
|
777
785
|
#answeredResult;
|
|
778
786
|
#authPromise;
|
|
779
787
|
#authSigner;
|
|
780
|
-
|
|
788
|
+
/** `close()`/`disconnect()` was called; cleared by the next `connect()`. */
|
|
789
|
+
#manualStop = false;
|
|
781
790
|
#reconnectAttempts = 0;
|
|
782
791
|
#reconnectTimer;
|
|
783
|
-
#skipReconnect = false;
|
|
784
|
-
/** Collapses error+close pairs into a single terminal/reconnect action. */
|
|
785
|
-
#deathHandled = false;
|
|
786
792
|
#enablePing;
|
|
787
793
|
#ping;
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
* Fired when a delivered EVENT fails id/signature verification, just before the event is dropped.
|
|
796
|
-
* A poisoned verifier dropping events does not fire it.
|
|
797
|
-
*/
|
|
798
|
-
oninvalidevent;
|
|
794
|
+
#listeners = {
|
|
795
|
+
notice: /* @__PURE__ */ new Set(),
|
|
796
|
+
close: /* @__PURE__ */ new Set(),
|
|
797
|
+
auth: /* @__PURE__ */ new Set(),
|
|
798
|
+
reconnect: /* @__PURE__ */ new Set(),
|
|
799
|
+
invalidevent: /* @__PURE__ */ new Set()
|
|
800
|
+
};
|
|
799
801
|
constructor(url, opts = {}) {
|
|
800
802
|
this.url = normalizeURL(url);
|
|
801
803
|
this.#WS = opts.websocketImplementation ?? getWebSocketImplementation();
|
|
@@ -823,7 +825,7 @@ var Relay = class Relay {
|
|
|
823
825
|
liveByFp: this.#liveByFp,
|
|
824
826
|
liveBySubId: this.#liveBySubId,
|
|
825
827
|
subs: this.#subs,
|
|
826
|
-
connected: () => this.#
|
|
828
|
+
connected: () => this.#isOpen(),
|
|
827
829
|
enableReconnect: () => this.#enableReconnect,
|
|
828
830
|
send: (message) => this.#send(message),
|
|
829
831
|
scheduleReconnect: () => this.#scheduleReconnect(),
|
|
@@ -836,11 +838,17 @@ var Relay = class Relay {
|
|
|
836
838
|
return this.#challenge;
|
|
837
839
|
}
|
|
838
840
|
get connected() {
|
|
839
|
-
return this.#
|
|
841
|
+
return this.#isOpen();
|
|
840
842
|
}
|
|
841
843
|
get status() {
|
|
842
844
|
return this.#status;
|
|
843
845
|
}
|
|
846
|
+
#isOpen() {
|
|
847
|
+
return this.#status === RelayStatus.Connected;
|
|
848
|
+
}
|
|
849
|
+
#setStatus(next) {
|
|
850
|
+
this.#status = next;
|
|
851
|
+
}
|
|
844
852
|
get generation() {
|
|
845
853
|
return this.#gen;
|
|
846
854
|
}
|
|
@@ -855,26 +863,38 @@ var Relay = class Relay {
|
|
|
855
863
|
get inFlightCount() {
|
|
856
864
|
return this.#publishes.size + this.#counts.size + this.#neg.size;
|
|
857
865
|
}
|
|
866
|
+
/**
|
|
867
|
+
* Listen for a relay event. Multiple listeners per type are allowed and fire in registration
|
|
868
|
+
* order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe
|
|
869
|
+
* function.
|
|
870
|
+
*/
|
|
871
|
+
on(type, listener) {
|
|
872
|
+
const set = this.#listeners[type];
|
|
873
|
+
set.add(listener);
|
|
874
|
+
return () => {
|
|
875
|
+
set.delete(listener);
|
|
876
|
+
};
|
|
877
|
+
}
|
|
878
|
+
#emit(type, payload) {
|
|
879
|
+
for (const listener of [...this.#listeners[type]]) invokeSafely(() => listener(payload));
|
|
880
|
+
}
|
|
858
881
|
static async connect(url, opts) {
|
|
859
882
|
const relay = new Relay(url, opts);
|
|
860
883
|
await relay.connect({ signal: opts?.signal });
|
|
861
884
|
return relay;
|
|
862
885
|
}
|
|
863
886
|
async connect(opts) {
|
|
864
|
-
if (this.#
|
|
887
|
+
if (this.#isOpen()) return;
|
|
865
888
|
if (this.#connecting) {
|
|
866
889
|
await raceSignal(this.#connecting, opts?.signal);
|
|
867
890
|
return;
|
|
868
891
|
}
|
|
869
892
|
throwIfAborted(opts?.signal);
|
|
870
893
|
const gen = ++this.#gen;
|
|
871
|
-
this.#
|
|
872
|
-
this.#skipReconnect = false;
|
|
873
|
-
this.#deathHandled = false;
|
|
894
|
+
this.#manualStop = false;
|
|
874
895
|
this.#clearReconnectTimer();
|
|
875
|
-
this.#
|
|
896
|
+
this.#setStatus(RelayStatus.Connecting);
|
|
876
897
|
const timeoutMs = opts?.timeoutMs ?? this.#connectTimeoutMs;
|
|
877
|
-
const isReconnect = this.#reconnectAttempts > 0;
|
|
878
898
|
let resolveConnect = noop;
|
|
879
899
|
let rejectConnect = noop;
|
|
880
900
|
let settled = false;
|
|
@@ -885,12 +905,7 @@ var Relay = class Relay {
|
|
|
885
905
|
});
|
|
886
906
|
const release = () => {
|
|
887
907
|
if (ws === void 0) return;
|
|
888
|
-
|
|
889
|
-
ws.removeEventListener("open", handlers.onOpen);
|
|
890
|
-
ws.removeEventListener("error", handlers.onError);
|
|
891
|
-
ws.removeEventListener("close", handlers.onClose);
|
|
892
|
-
ws.removeEventListener("message", handlers.onMessage);
|
|
893
|
-
} catch {}
|
|
908
|
+
this.#removeSocketListeners(handlers);
|
|
894
909
|
if (this.#socketHandlers === handlers) this.#socketHandlers = void 0;
|
|
895
910
|
try {
|
|
896
911
|
if (ws.readyState !== this.#WS.CLOSED && ws.readyState !== this.#WS.CLOSING) ws.close();
|
|
@@ -914,7 +929,6 @@ var Relay = class Relay {
|
|
|
914
929
|
release();
|
|
915
930
|
return;
|
|
916
931
|
}
|
|
917
|
-
if (!isReconnect && !this.#enableReconnect) this.#skipReconnect = true;
|
|
918
932
|
release();
|
|
919
933
|
finish(new RelayTimeoutError("connection timed out", this.url));
|
|
920
934
|
if (gen === this.#gen) this.#handleSocketDeath("connection timed out", {
|
|
@@ -926,7 +940,6 @@ var Relay = class Relay {
|
|
|
926
940
|
try {
|
|
927
941
|
ws = new this.#WS(this.url);
|
|
928
942
|
} catch (error) {
|
|
929
|
-
if (!isReconnect && !this.#enableReconnect) this.#skipReconnect = true;
|
|
930
943
|
finish(error);
|
|
931
944
|
if (gen === this.#gen) this.#handleSocketDeath("connection failed", {
|
|
932
945
|
fromConnectAttempt: true,
|
|
@@ -941,9 +954,7 @@ var Relay = class Relay {
|
|
|
941
954
|
release();
|
|
942
955
|
return;
|
|
943
956
|
}
|
|
944
|
-
this.#
|
|
945
|
-
this.#status = RelayStatus.Connected;
|
|
946
|
-
this.#deathHandled = false;
|
|
957
|
+
this.#setStatus(RelayStatus.Connected);
|
|
947
958
|
const wasReconnect = this.#reconnectAttempts > 0;
|
|
948
959
|
this.#challenge = void 0;
|
|
949
960
|
this.#authPromise = void 0;
|
|
@@ -951,18 +962,16 @@ var Relay = class Relay {
|
|
|
951
962
|
this.#answeredChallenge = void 0;
|
|
952
963
|
this.#answeredResult = void 0;
|
|
953
964
|
if (!resubscribeAll(this.#live)) {
|
|
954
|
-
this.#
|
|
955
|
-
this.#status = RelayStatus.Disconnected;
|
|
956
|
-
if (!isReconnect && !this.#enableReconnect) this.#skipReconnect = true;
|
|
965
|
+
this.#setStatus(RelayStatus.Disconnected);
|
|
957
966
|
release();
|
|
958
967
|
finish(new RelayConnectionError("connection failed", this.url));
|
|
959
|
-
if (this.#enableReconnect && !this.#
|
|
968
|
+
if (this.#enableReconnect && !this.#manualStop && this.#subs.size > 0) this.#scheduleReconnect();
|
|
960
969
|
return;
|
|
961
970
|
}
|
|
962
971
|
this.#reconnectAttempts = 0;
|
|
963
972
|
if (this.#enablePing) this.#ping.start();
|
|
964
973
|
else this.#ping.stop();
|
|
965
|
-
if (wasReconnect)
|
|
974
|
+
if (wasReconnect) this.#emit("reconnect", void 0);
|
|
966
975
|
finish();
|
|
967
976
|
};
|
|
968
977
|
const onError = () => {
|
|
@@ -970,9 +979,7 @@ var Relay = class Relay {
|
|
|
970
979
|
release();
|
|
971
980
|
return;
|
|
972
981
|
}
|
|
973
|
-
this.#connected = false;
|
|
974
982
|
const fromConnectAttempt = !settled;
|
|
975
|
-
if (!settled && !isReconnect && !this.#enableReconnect) this.#skipReconnect = true;
|
|
976
983
|
release();
|
|
977
984
|
finish(new RelayConnectionError("connection failed", this.url));
|
|
978
985
|
if (gen === this.#gen) this.#handleSocketDeath("connection failed", {
|
|
@@ -985,9 +992,7 @@ var Relay = class Relay {
|
|
|
985
992
|
release();
|
|
986
993
|
return;
|
|
987
994
|
}
|
|
988
|
-
this.#connected = false;
|
|
989
995
|
const fromConnectAttempt = !settled;
|
|
990
|
-
if (!settled && !isReconnect && !this.#enableReconnect) this.#skipReconnect = true;
|
|
991
996
|
release();
|
|
992
997
|
if (fromConnectAttempt) finish(new RelayConnectionError("websocket closed", this.url));
|
|
993
998
|
if (gen === this.#gen) this.#handleSocketDeath("websocket closed", {
|
|
@@ -1014,23 +1019,25 @@ var Relay = class Relay {
|
|
|
1014
1019
|
ws.addEventListener("message", onMessage);
|
|
1015
1020
|
await raceSignal(connecting, opts?.signal);
|
|
1016
1021
|
}
|
|
1017
|
-
#
|
|
1018
|
-
const h = this.#socketHandlers;
|
|
1019
|
-
if (!h) return;
|
|
1022
|
+
#removeSocketListeners(h) {
|
|
1020
1023
|
try {
|
|
1021
1024
|
h.ws.removeEventListener("open", h.onOpen);
|
|
1022
1025
|
h.ws.removeEventListener("error", h.onError);
|
|
1023
1026
|
h.ws.removeEventListener("close", h.onClose);
|
|
1024
1027
|
h.ws.removeEventListener("message", h.onMessage);
|
|
1025
1028
|
} catch {}
|
|
1029
|
+
}
|
|
1030
|
+
#detachSocketHandlers() {
|
|
1031
|
+
const h = this.#socketHandlers;
|
|
1032
|
+
if (!h) return;
|
|
1033
|
+
this.#removeSocketListeners(h);
|
|
1026
1034
|
this.#socketHandlers = void 0;
|
|
1027
1035
|
}
|
|
1028
1036
|
/** Graceful shutdown: disables reconnect and closes all subscriptions. */
|
|
1029
1037
|
close() {
|
|
1030
1038
|
this.#gen += 1;
|
|
1031
|
-
this.#
|
|
1032
|
-
this.#
|
|
1033
|
-
this.#skipReconnect = true;
|
|
1039
|
+
this.#setStatus(RelayStatus.Closed);
|
|
1040
|
+
this.#manualStop = true;
|
|
1034
1041
|
this.#clearReconnectTimer();
|
|
1035
1042
|
if (this.#connectTimer !== void 0) {
|
|
1036
1043
|
clearTimeout(this.#connectTimer);
|
|
@@ -1041,13 +1048,10 @@ var Relay = class Relay {
|
|
|
1041
1048
|
try {
|
|
1042
1049
|
closeAllSubscriptions(this.#live, "relay closed");
|
|
1043
1050
|
} finally {
|
|
1044
|
-
this.#
|
|
1045
|
-
this.#rejectCounts(new RelayClosedError("relay closed", this.url));
|
|
1046
|
-
this.#rejectNeg(new RelayClosedError("relay closed", this.url));
|
|
1051
|
+
this.#failPending(new RelayClosedError("relay closed", this.url));
|
|
1047
1052
|
this.#detachSocketHandlers();
|
|
1048
1053
|
this.#teardownSocket();
|
|
1049
|
-
this.#
|
|
1050
|
-
invokeSafely(() => this.onclose?.());
|
|
1054
|
+
this.#emit("close", void 0);
|
|
1051
1055
|
}
|
|
1052
1056
|
}
|
|
1053
1057
|
/**
|
|
@@ -1057,9 +1061,8 @@ var Relay = class Relay {
|
|
|
1057
1061
|
*/
|
|
1058
1062
|
disconnect() {
|
|
1059
1063
|
this.#gen += 1;
|
|
1060
|
-
this.#
|
|
1061
|
-
this.#
|
|
1062
|
-
this.#skipReconnect = true;
|
|
1064
|
+
this.#setStatus(RelayStatus.Disconnected);
|
|
1065
|
+
this.#manualStop = true;
|
|
1063
1066
|
this.#clearReconnectTimer();
|
|
1064
1067
|
if (this.#connectTimer !== void 0) {
|
|
1065
1068
|
clearTimeout(this.#connectTimer);
|
|
@@ -1067,12 +1070,9 @@ var Relay = class Relay {
|
|
|
1067
1070
|
}
|
|
1068
1071
|
this.#ping.stop();
|
|
1069
1072
|
this.#connectFinish?.(new RelayClosedError("relay disconnected", this.url));
|
|
1070
|
-
this.#
|
|
1071
|
-
this.#rejectCounts(new RelayClosedError("relay disconnected", this.url));
|
|
1072
|
-
this.#rejectNeg(new RelayClosedError("relay disconnected", this.url));
|
|
1073
|
+
this.#failPending(new RelayClosedError("relay disconnected", this.url));
|
|
1073
1074
|
this.#detachSocketHandlers();
|
|
1074
1075
|
this.#teardownSocket();
|
|
1075
|
-
this.#connected = false;
|
|
1076
1076
|
}
|
|
1077
1077
|
#teardownSocket() {
|
|
1078
1078
|
try {
|
|
@@ -1104,49 +1104,50 @@ var Relay = class Relay {
|
|
|
1104
1104
|
for (const session of this.#neg.values()) failNegSession(session, err);
|
|
1105
1105
|
this.#neg.clear();
|
|
1106
1106
|
}
|
|
1107
|
+
#failPending(err) {
|
|
1108
|
+
this.#rejectPublishes(err);
|
|
1109
|
+
this.#rejectCounts(err);
|
|
1110
|
+
this.#rejectNeg(err);
|
|
1111
|
+
}
|
|
1107
1112
|
/** Unexpected socket death. Keep subscriptions if reconnecting. */
|
|
1108
1113
|
#handleSocketDeath(reason, opts) {
|
|
1109
1114
|
if (opts.gen !== this.#gen) return;
|
|
1110
|
-
|
|
1111
|
-
this.#deathHandled = true;
|
|
1115
|
+
this.#gen += 1;
|
|
1112
1116
|
this.#ping.stop();
|
|
1113
1117
|
this.#detachSocketHandlers();
|
|
1114
|
-
this.#connected = false;
|
|
1115
1118
|
this.#ws = void 0;
|
|
1116
|
-
this.#
|
|
1117
|
-
this.#
|
|
1118
|
-
|
|
1119
|
-
if (this.#enableReconnect && !this.#intentionalClose && !this.#skipReconnect && this.#subs.size > 0) {
|
|
1120
|
-
this.#status = RelayStatus.Disconnected;
|
|
1119
|
+
this.#failPending(new RelayClosedError(reason, this.url));
|
|
1120
|
+
if (this.#enableReconnect && !this.#manualStop && this.#subs.size > 0) {
|
|
1121
|
+
this.#setStatus(RelayStatus.Disconnected);
|
|
1121
1122
|
this.#scheduleReconnect();
|
|
1122
1123
|
return;
|
|
1123
1124
|
}
|
|
1124
|
-
|
|
1125
|
+
this.#setStatus(RelayStatus.Disconnected);
|
|
1125
1126
|
if (opts.fromConnectAttempt !== true || this.#subs.size > 0) {
|
|
1126
1127
|
closeAllSubscriptions(this.#live, reason);
|
|
1127
|
-
if (!this.#
|
|
1128
|
+
if (!this.#manualStop) this.#emit("close", void 0);
|
|
1128
1129
|
}
|
|
1129
|
-
this.#
|
|
1130
|
+
this.#setStatus(RelayStatus.Closed);
|
|
1130
1131
|
}
|
|
1131
1132
|
#scheduleReconnect() {
|
|
1132
1133
|
if (this.#reconnectTimer !== void 0) return;
|
|
1133
|
-
if (this.#
|
|
1134
|
+
if (this.#isOpen()) return;
|
|
1134
1135
|
const delay = this.#backoff[Math.min(this.#reconnectAttempts, this.#backoff.length - 1)] ?? 6e4;
|
|
1135
1136
|
this.#reconnectAttempts += 1;
|
|
1136
1137
|
this.#reconnectTimer = setTimeout(() => {
|
|
1137
1138
|
this.#reconnectTimer = void 0;
|
|
1138
|
-
if (this.#
|
|
1139
|
+
if (this.#manualStop || this.#isOpen()) return;
|
|
1139
1140
|
(async () => {
|
|
1140
1141
|
try {
|
|
1141
1142
|
await this.connect();
|
|
1142
1143
|
} catch {
|
|
1143
|
-
if (this.#enableReconnect && !this.#
|
|
1144
|
+
if (this.#enableReconnect && !this.#manualStop && this.#subs.size > 0 && !this.#isOpen()) this.#scheduleReconnect();
|
|
1144
1145
|
}
|
|
1145
1146
|
})();
|
|
1146
1147
|
}, delay);
|
|
1147
1148
|
}
|
|
1148
1149
|
#send(message) {
|
|
1149
|
-
if (!this.#ws || !this.#
|
|
1150
|
+
if (!this.#ws || !this.#isOpen()) throw new RelayClosedError("not connected", this.url);
|
|
1150
1151
|
const raw = typeof message === "string" ? message : encodeClientMessage(message);
|
|
1151
1152
|
this.#ws.send(raw);
|
|
1152
1153
|
}
|
|
@@ -1233,7 +1234,7 @@ var Relay = class Relay {
|
|
|
1233
1234
|
}
|
|
1234
1235
|
case "NOTICE": {
|
|
1235
1236
|
const [, notice] = msg;
|
|
1236
|
-
|
|
1237
|
+
this.#emit("notice", notice);
|
|
1237
1238
|
break;
|
|
1238
1239
|
}
|
|
1239
1240
|
case "AUTH": {
|
|
@@ -1245,7 +1246,7 @@ var Relay = class Relay {
|
|
|
1245
1246
|
this.#answeredResult = void 0;
|
|
1246
1247
|
}
|
|
1247
1248
|
this.#challenge = authChallenge;
|
|
1248
|
-
|
|
1249
|
+
this.#emit("auth", authChallenge);
|
|
1249
1250
|
break;
|
|
1250
1251
|
}
|
|
1251
1252
|
}
|
|
@@ -1253,7 +1254,7 @@ var Relay = class Relay {
|
|
|
1253
1254
|
/** Low-level REQ with callbacks. Survives reconnect when enableReconnect is on. */
|
|
1254
1255
|
subscribe(filters, opts = {}) {
|
|
1255
1256
|
if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
|
|
1256
|
-
if (!this.#
|
|
1257
|
+
if (!this.#isOpen() && !this.#enableReconnect) throw new RelayClosedError("not connected", this.url);
|
|
1257
1258
|
const canonical = canonicalizeFilters(filters);
|
|
1258
1259
|
if (opts.closeOnEose === true) return openExclusive(this.#live, canonical, opts);
|
|
1259
1260
|
return subscribeLive(this.#live, canonical, opts);
|
|
@@ -1262,12 +1263,12 @@ var Relay = class Relay {
|
|
|
1262
1263
|
if (this.#verifyDead) return false;
|
|
1263
1264
|
try {
|
|
1264
1265
|
const ok = this.#verify(event);
|
|
1265
|
-
if (!ok)
|
|
1266
|
+
if (!ok) this.#emit("invalidevent", void 0);
|
|
1266
1267
|
return ok;
|
|
1267
1268
|
} catch (error) {
|
|
1268
|
-
if (error instanceof
|
|
1269
|
+
if (error instanceof WasmPoisonedError) {
|
|
1269
1270
|
this.#verifyDead = true;
|
|
1270
|
-
|
|
1271
|
+
this.#emit("notice", "wasm-poisoned: instance aborted");
|
|
1271
1272
|
return false;
|
|
1272
1273
|
}
|
|
1273
1274
|
throw error;
|
|
@@ -1285,53 +1286,61 @@ var Relay = class Relay {
|
|
|
1285
1286
|
async fetch(filters, opts) {
|
|
1286
1287
|
if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
|
|
1287
1288
|
const canonical = canonicalizeFilters(filters);
|
|
1288
|
-
if (!this.#
|
|
1289
|
+
if (!this.#isOpen()) await this.connect({ signal: opts?.signal });
|
|
1289
1290
|
return fetchFilters((f, o) => this.subscribe(f, o), canonical, {
|
|
1290
1291
|
timeoutMs: opts?.timeoutMs ?? 4400,
|
|
1291
1292
|
signal: opts?.signal,
|
|
1292
1293
|
id: opts?.id
|
|
1293
1294
|
});
|
|
1294
1295
|
}
|
|
1295
|
-
/**
|
|
1296
|
+
/**
|
|
1297
|
+
* Publish an event and wait for OK. A concurrent publish of the same event id joins the in-flight
|
|
1298
|
+
* publish instead of racing it — the joiner's `timeoutMs` does not apply.
|
|
1299
|
+
*/
|
|
1296
1300
|
async publish(event, opts) {
|
|
1297
|
-
if (!this.#
|
|
1301
|
+
if (!this.#isOpen()) throw new RelayClosedError("not connected", this.url);
|
|
1302
|
+
const existing = this.#publishes.get(event.id);
|
|
1303
|
+
if (existing?.event !== void 0) return existing.promise;
|
|
1298
1304
|
const timeoutMs = opts?.timeoutMs ?? this.#publishTimeoutMs;
|
|
1299
|
-
|
|
1300
|
-
const
|
|
1301
|
-
this.#publishes.delete(event.id);
|
|
1302
|
-
reject(new RelayTimeoutError("publish timed out", this.url));
|
|
1303
|
-
}, timeoutMs);
|
|
1304
|
-
this.#publishes.set(event.id, {
|
|
1305
|
+
const promise = new Promise((resolve, reject) => {
|
|
1306
|
+
const waiter = {
|
|
1305
1307
|
resolve,
|
|
1306
1308
|
reject,
|
|
1307
|
-
timer
|
|
1309
|
+
timer: setTimeout(() => {
|
|
1310
|
+
if (this.#publishes.get(event.id) === waiter) {
|
|
1311
|
+
this.#publishes.delete(event.id);
|
|
1312
|
+
reject(new RelayTimeoutError("publish timed out", this.url));
|
|
1313
|
+
}
|
|
1314
|
+
}, timeoutMs),
|
|
1308
1315
|
event,
|
|
1309
|
-
timeoutMs
|
|
1310
|
-
|
|
1316
|
+
timeoutMs,
|
|
1317
|
+
get promise() {
|
|
1318
|
+
return promise;
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
this.#publishes.set(event.id, waiter);
|
|
1311
1322
|
try {
|
|
1312
1323
|
this.#send(["EVENT", event]);
|
|
1313
1324
|
} catch (error) {
|
|
1314
|
-
clearTimeout(timer);
|
|
1315
|
-
this.#publishes.delete(event.id);
|
|
1325
|
+
clearTimeout(waiter.timer);
|
|
1326
|
+
if (this.#publishes.get(event.id) === waiter) this.#publishes.delete(event.id);
|
|
1316
1327
|
reject(error instanceof Error ? error : new RelayPublishError("publish failed", this.url));
|
|
1317
1328
|
}
|
|
1318
1329
|
});
|
|
1330
|
+
return promise;
|
|
1319
1331
|
}
|
|
1320
1332
|
/**
|
|
1321
1333
|
* NIP-45 COUNT: ask the relay how many events match `filters`. Resolves with the COUNT payload
|
|
1322
1334
|
* (count / optional approximate / optional hll).
|
|
1323
1335
|
*/
|
|
1324
1336
|
async count(filters, opts) {
|
|
1325
|
-
if (!this.#
|
|
1337
|
+
if (!this.#isOpen()) throw new RelayClosedError("not connected", this.url);
|
|
1326
1338
|
if (filters.length === 0) throw new RelayError("COUNT requires at least one filter", this.url);
|
|
1327
1339
|
throwIfAborted(opts?.signal);
|
|
1328
1340
|
const canonical = canonicalizeFilters(filters);
|
|
1329
1341
|
const id = opts?.id === void 0 ? this.nextSubId("count") : createSubscriptionId(opts.id);
|
|
1330
1342
|
const timeoutMs = opts?.timeoutMs ?? this.#publishTimeoutMs;
|
|
1331
1343
|
return new Promise((resolve, reject) => {
|
|
1332
|
-
const cleanup = () => {
|
|
1333
|
-
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1334
|
-
};
|
|
1335
1344
|
const waiter = {
|
|
1336
1345
|
resolve: (result) => {
|
|
1337
1346
|
if (waiter.timer !== void 0) clearTimeout(waiter.timer);
|
|
@@ -1352,14 +1361,13 @@ var Relay = class Relay {
|
|
|
1352
1361
|
authRetried: false,
|
|
1353
1362
|
timeoutMs
|
|
1354
1363
|
};
|
|
1355
|
-
const
|
|
1364
|
+
const cleanup = onAbort(opts?.signal, () => {
|
|
1356
1365
|
if (opts?.signal) waiter.reject(abortReason(opts.signal));
|
|
1357
|
-
};
|
|
1366
|
+
});
|
|
1358
1367
|
waiter.timer = setTimeout(() => {
|
|
1359
1368
|
waiter.reject(new RelayTimeoutError("count timed out", this.url));
|
|
1360
1369
|
}, timeoutMs);
|
|
1361
1370
|
this.#counts.set(id, waiter);
|
|
1362
|
-
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
1363
1371
|
try {
|
|
1364
1372
|
this.#send([
|
|
1365
1373
|
"COUNT",
|
|
@@ -1389,14 +1397,10 @@ var Relay = class Relay {
|
|
|
1389
1397
|
};
|
|
1390
1398
|
throw error;
|
|
1391
1399
|
}
|
|
1392
|
-
if (!this.#
|
|
1400
|
+
if (!this.#isOpen()) throw new RelayClosedError("not connected", this.url);
|
|
1393
1401
|
const timeoutMs = opts?.timeoutMs ?? this.#publishTimeoutMs;
|
|
1394
|
-
|
|
1395
|
-
const
|
|
1396
|
-
this.#publishes.delete(event.id);
|
|
1397
|
-
reject(new RelayTimeoutError("auth timed out", this.url));
|
|
1398
|
-
}, timeoutMs);
|
|
1399
|
-
this.#publishes.set(event.id, {
|
|
1402
|
+
const authPromise = new Promise((resolve, reject) => {
|
|
1403
|
+
const waiter = {
|
|
1400
1404
|
resolve: (result) => {
|
|
1401
1405
|
if (this.#challenge === challenge) {
|
|
1402
1406
|
this.#answeredChallenge = challenge;
|
|
@@ -1405,17 +1409,27 @@ var Relay = class Relay {
|
|
|
1405
1409
|
resolve(result);
|
|
1406
1410
|
},
|
|
1407
1411
|
reject,
|
|
1408
|
-
timer
|
|
1409
|
-
|
|
1410
|
-
|
|
1412
|
+
timer: setTimeout(() => {
|
|
1413
|
+
if (this.#publishes.get(event.id) === waiter) {
|
|
1414
|
+
this.#publishes.delete(event.id);
|
|
1415
|
+
reject(new RelayTimeoutError("auth timed out", this.url));
|
|
1416
|
+
}
|
|
1417
|
+
}, timeoutMs),
|
|
1418
|
+
timeoutMs,
|
|
1419
|
+
get promise() {
|
|
1420
|
+
return authPromise;
|
|
1421
|
+
}
|
|
1422
|
+
};
|
|
1423
|
+
this.#publishes.set(event.id, waiter);
|
|
1411
1424
|
try {
|
|
1412
1425
|
this.#send(["AUTH", event]);
|
|
1413
1426
|
} catch (error) {
|
|
1414
|
-
clearTimeout(timer);
|
|
1415
|
-
this.#publishes.delete(event.id);
|
|
1427
|
+
clearTimeout(waiter.timer);
|
|
1428
|
+
if (this.#publishes.get(event.id) === waiter) this.#publishes.delete(event.id);
|
|
1416
1429
|
reject(error instanceof Error ? error : new RelayPublishError("auth send failed", this.url));
|
|
1417
1430
|
}
|
|
1418
1431
|
});
|
|
1432
|
+
return authPromise;
|
|
1419
1433
|
})();
|
|
1420
1434
|
this.#authPromise = pending;
|
|
1421
1435
|
try {
|
|
@@ -1428,8 +1442,8 @@ var Relay = class Relay {
|
|
|
1428
1442
|
}
|
|
1429
1443
|
/**
|
|
1430
1444
|
* Clear a cached AUTH rejection for the current challenge so a later `auth()` signs again. A
|
|
1431
|
-
* successful auth and in-flight answers are kept. When a challenge is pending and unanswered,
|
|
1432
|
-
* `
|
|
1445
|
+
* successful auth and in-flight answers are kept. When a challenge is pending and unanswered, an
|
|
1446
|
+
* `auth` event is re-fired so the pool's automatic auth can run once more.
|
|
1433
1447
|
*/
|
|
1434
1448
|
resetAuth() {
|
|
1435
1449
|
if (this.#answeredResult !== void 0 && !this.#answeredResult.ok) {
|
|
@@ -1438,7 +1452,7 @@ var Relay = class Relay {
|
|
|
1438
1452
|
}
|
|
1439
1453
|
if (this.#challenge !== void 0 && this.#challenge !== this.#authedChallenge) {
|
|
1440
1454
|
const challenge = this.#challenge;
|
|
1441
|
-
|
|
1455
|
+
this.#emit("auth", challenge);
|
|
1442
1456
|
}
|
|
1443
1457
|
}
|
|
1444
1458
|
async #ensureAuthed() {
|
|
@@ -1463,7 +1477,7 @@ var Relay = class Relay {
|
|
|
1463
1477
|
dropSubscription(this.#live, sub, reason);
|
|
1464
1478
|
return;
|
|
1465
1479
|
}
|
|
1466
|
-
if (sub.closed || !this.#
|
|
1480
|
+
if (sub.closed || !this.#isOpen()) {
|
|
1467
1481
|
dropSubscription(this.#live, sub, reason);
|
|
1468
1482
|
return;
|
|
1469
1483
|
}
|
|
@@ -1494,7 +1508,7 @@ var Relay = class Relay {
|
|
|
1494
1508
|
fail();
|
|
1495
1509
|
return;
|
|
1496
1510
|
}
|
|
1497
|
-
if (!this.#
|
|
1511
|
+
if (!this.#isOpen()) {
|
|
1498
1512
|
fail();
|
|
1499
1513
|
return;
|
|
1500
1514
|
}
|
|
@@ -1518,7 +1532,7 @@ var Relay = class Relay {
|
|
|
1518
1532
|
}
|
|
1519
1533
|
const finish = (result) => {
|
|
1520
1534
|
if (waiter.timer !== void 0) clearTimeout(waiter.timer);
|
|
1521
|
-
this.#publishes.delete(eventId);
|
|
1535
|
+
if (this.#publishes.get(eventId) === waiter) this.#publishes.delete(eventId);
|
|
1522
1536
|
waiter.resolve(result);
|
|
1523
1537
|
};
|
|
1524
1538
|
try {
|
|
@@ -1538,7 +1552,7 @@ var Relay = class Relay {
|
|
|
1538
1552
|
});
|
|
1539
1553
|
return;
|
|
1540
1554
|
}
|
|
1541
|
-
if (!this.#
|
|
1555
|
+
if (!this.#isOpen()) {
|
|
1542
1556
|
finish({
|
|
1543
1557
|
ok: false,
|
|
1544
1558
|
message
|
|
@@ -1546,8 +1560,10 @@ var Relay = class Relay {
|
|
|
1546
1560
|
return;
|
|
1547
1561
|
}
|
|
1548
1562
|
waiter.timer = setTimeout(() => {
|
|
1549
|
-
this.#publishes.
|
|
1550
|
-
|
|
1563
|
+
if (this.#publishes.get(eventId) === waiter) {
|
|
1564
|
+
this.#publishes.delete(eventId);
|
|
1565
|
+
waiter.reject(new RelayTimeoutError("publish timed out", this.url));
|
|
1566
|
+
}
|
|
1551
1567
|
}, waiter.timeoutMs);
|
|
1552
1568
|
this.#send(["EVENT", waiter.event]);
|
|
1553
1569
|
} catch {
|
|
@@ -1566,7 +1582,7 @@ var Relay = class Relay {
|
|
|
1566
1582
|
* `NEG-MSG`), not a per-message budget. Default: {@link RelayOptions.publishTimeoutMs}.
|
|
1567
1583
|
*/
|
|
1568
1584
|
async negReconcile(filter, storage, opts) {
|
|
1569
|
-
if (!this.#
|
|
1585
|
+
if (!this.#isOpen()) throw new RelayClosedError("not connected", this.url);
|
|
1570
1586
|
throwIfAborted(opts?.signal);
|
|
1571
1587
|
const canonical = canonicalizeFilter(filter);
|
|
1572
1588
|
const id = opts?.id === void 0 ? this.nextSubId("neg") : assertSubscriptionId(opts.id);
|
|
@@ -1602,565 +1618,6 @@ var Relay = class Relay {
|
|
|
1602
1618
|
}
|
|
1603
1619
|
};
|
|
1604
1620
|
//#endregion
|
|
1605
|
-
|
|
1606
|
-
/**
|
|
1607
|
-
* Aggregate EOSE/close across routed jobs. One `seen` set. `pending` counts URL list entries
|
|
1608
|
-
* (duplicates included). `pendingEose` counts unique `jobIndex:url` keys.
|
|
1609
|
-
*/
|
|
1610
|
-
function fanIn(pool, jobs, opts = {}) {
|
|
1611
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1612
|
-
const closers = [];
|
|
1613
|
-
let closed = false;
|
|
1614
|
-
let eoseFired = false;
|
|
1615
|
-
let sawEose = false;
|
|
1616
|
-
let eoseTimer;
|
|
1617
|
-
const eoseDone = /* @__PURE__ */ new Set();
|
|
1618
|
-
const eoseAttempted = /* @__PURE__ */ new Set();
|
|
1619
|
-
let pendingEose = 0;
|
|
1620
|
-
let pending = 0;
|
|
1621
|
-
const fireEose = () => {
|
|
1622
|
-
if (closed || eoseFired) return;
|
|
1623
|
-
eoseFired = true;
|
|
1624
|
-
if (eoseTimer !== void 0) {
|
|
1625
|
-
clearTimeout(eoseTimer);
|
|
1626
|
-
eoseTimer = void 0;
|
|
1627
|
-
}
|
|
1628
|
-
invokeSafely(() => opts.oneose?.());
|
|
1629
|
-
};
|
|
1630
|
-
/**
|
|
1631
|
-
* Remove one relay from the pending-EOSE set. `eosed` credits a real EOSE; a CLOSED or a connect
|
|
1632
|
-
* failure only drops the wait — the aggregate `oneose` fires once every remaining relay ended AND
|
|
1633
|
-
* at least one actually EOSE'd.
|
|
1634
|
-
*/
|
|
1635
|
-
const settleEose = (jobIndex, url, eosed) => {
|
|
1636
|
-
const key = `${jobIndex}:${url}`;
|
|
1637
|
-
if (eoseDone.has(key)) return;
|
|
1638
|
-
eoseDone.add(key);
|
|
1639
|
-
if (eosed) sawEose = true;
|
|
1640
|
-
pendingEose -= 1;
|
|
1641
|
-
if (pendingEose === 0 && sawEose) fireEose();
|
|
1642
|
-
};
|
|
1643
|
-
const settleClose = () => {
|
|
1644
|
-
closed = true;
|
|
1645
|
-
if (eoseTimer !== void 0) {
|
|
1646
|
-
clearTimeout(eoseTimer);
|
|
1647
|
-
eoseTimer = void 0;
|
|
1648
|
-
}
|
|
1649
|
-
};
|
|
1650
|
-
const closeAll = (reason) => {
|
|
1651
|
-
if (closed) return;
|
|
1652
|
-
settleClose();
|
|
1653
|
-
for (const c of closers) c.close(reason);
|
|
1654
|
-
invokeSafely(() => opts.onclose?.(reason ?? "closed by client"));
|
|
1655
|
-
};
|
|
1656
|
-
if (opts.signal?.aborted === true) {
|
|
1657
|
-
closed = true;
|
|
1658
|
-
invokeSafely(() => opts.onclose?.("aborted"));
|
|
1659
|
-
return { close: closeAll };
|
|
1660
|
-
}
|
|
1661
|
-
opts.signal?.addEventListener("abort", () => closeAll("aborted"), { once: true });
|
|
1662
|
-
const failUrl = (jobIndex, key) => {
|
|
1663
|
-
settleEose(jobIndex, key, false);
|
|
1664
|
-
pending -= 1;
|
|
1665
|
-
if (pending <= 0 && closers.length === 0 && !closed) {
|
|
1666
|
-
settleClose();
|
|
1667
|
-
invokeSafely(() => opts.onclose?.("all relays failed"));
|
|
1668
|
-
}
|
|
1669
|
-
};
|
|
1670
|
-
const attach = (relay, job, jobIndex) => {
|
|
1671
|
-
if (closed) return;
|
|
1672
|
-
const received = opts.receivedEvent;
|
|
1673
|
-
const sub = relay.subscribe([...job.filters], {
|
|
1674
|
-
id: jobs.length === 1 ? job.id : void 0,
|
|
1675
|
-
closeOnEose: opts.closeOnEose,
|
|
1676
|
-
alreadyHaveEvent: (id) => {
|
|
1677
|
-
let have = seen.has(id);
|
|
1678
|
-
invokeSafely(() => {
|
|
1679
|
-
have = have || Boolean(opts.alreadyHaveEvent?.(id));
|
|
1680
|
-
});
|
|
1681
|
-
return have;
|
|
1682
|
-
},
|
|
1683
|
-
receivedEvent: received === void 0 ? void 0 : (id) => invokeSafely(() => received(id, relay.url)),
|
|
1684
|
-
onevent: (event) => {
|
|
1685
|
-
seen.add(event.id);
|
|
1686
|
-
invokeSafely(() => opts.onevent?.(event, relay.url));
|
|
1687
|
-
},
|
|
1688
|
-
oneose: () => settleEose(jobIndex, relay.url, true),
|
|
1689
|
-
onclose: (reason) => {
|
|
1690
|
-
settleEose(jobIndex, relay.url, false);
|
|
1691
|
-
pending -= 1;
|
|
1692
|
-
if (pending <= 0 && !closed) {
|
|
1693
|
-
settleClose();
|
|
1694
|
-
invokeSafely(() => opts.onclose?.(reason));
|
|
1695
|
-
}
|
|
1696
|
-
}
|
|
1697
|
-
});
|
|
1698
|
-
closers.push(sub);
|
|
1699
|
-
};
|
|
1700
|
-
for (const [jobIndex, job] of jobs.entries()) {
|
|
1701
|
-
const jobUrls = /* @__PURE__ */ new Set();
|
|
1702
|
-
for (const url of job.urls) {
|
|
1703
|
-
let key;
|
|
1704
|
-
try {
|
|
1705
|
-
key = normalizeURL(url);
|
|
1706
|
-
} catch {
|
|
1707
|
-
key = url;
|
|
1708
|
-
}
|
|
1709
|
-
if (jobUrls.has(key)) continue;
|
|
1710
|
-
jobUrls.add(key);
|
|
1711
|
-
pending += 1;
|
|
1712
|
-
const eoseKey = `${jobIndex}:${key}`;
|
|
1713
|
-
if (!eoseAttempted.has(eoseKey)) {
|
|
1714
|
-
eoseAttempted.add(eoseKey);
|
|
1715
|
-
pendingEose += 1;
|
|
1716
|
-
}
|
|
1717
|
-
const tryAttach = (relay) => {
|
|
1718
|
-
try {
|
|
1719
|
-
attach(relay, job, jobIndex);
|
|
1720
|
-
} catch (error) {
|
|
1721
|
-
if (error instanceof RelayClosedError) {
|
|
1722
|
-
failUrl(jobIndex, key);
|
|
1723
|
-
return;
|
|
1724
|
-
}
|
|
1725
|
-
throw error;
|
|
1726
|
-
}
|
|
1727
|
-
};
|
|
1728
|
-
(async () => {
|
|
1729
|
-
let relay;
|
|
1730
|
-
try {
|
|
1731
|
-
relay = await pool.ensureRelay(key, {
|
|
1732
|
-
signal: opts.signal,
|
|
1733
|
-
timeoutMs: opts.connectTimeoutMs
|
|
1734
|
-
});
|
|
1735
|
-
} catch {
|
|
1736
|
-
const existing = pool.getRelay(key);
|
|
1737
|
-
if (existing !== void 0) {
|
|
1738
|
-
tryAttach(existing);
|
|
1739
|
-
return;
|
|
1740
|
-
}
|
|
1741
|
-
failUrl(jobIndex, key);
|
|
1742
|
-
return;
|
|
1743
|
-
}
|
|
1744
|
-
tryAttach(relay);
|
|
1745
|
-
})();
|
|
1746
|
-
}
|
|
1747
|
-
}
|
|
1748
|
-
if (opts.eoseTimeoutMs !== void 0 && pending > 0) eoseTimer = setTimeout(() => {
|
|
1749
|
-
eoseTimer = void 0;
|
|
1750
|
-
fireEose();
|
|
1751
|
-
}, opts.eoseTimeoutMs);
|
|
1752
|
-
if (pending === 0) queueMicrotask(() => {
|
|
1753
|
-
if (closed) return;
|
|
1754
|
-
settleClose();
|
|
1755
|
-
invokeSafely(() => opts.onclose?.("no relays"));
|
|
1756
|
-
});
|
|
1757
|
-
return { close: closeAll };
|
|
1758
|
-
}
|
|
1759
|
-
async function fetchRouted(pool, jobs, opts = {}) {
|
|
1760
|
-
throwIfAborted(opts.signal);
|
|
1761
|
-
const byId = /* @__PURE__ */ new Map();
|
|
1762
|
-
await Promise.all(jobs.flatMap((job) => {
|
|
1763
|
-
const urls = [];
|
|
1764
|
-
for (const raw of job.urls) {
|
|
1765
|
-
let key;
|
|
1766
|
-
try {
|
|
1767
|
-
key = normalizeURL(raw);
|
|
1768
|
-
} catch {
|
|
1769
|
-
key = raw;
|
|
1770
|
-
}
|
|
1771
|
-
if (!urls.includes(key)) urls.push(key);
|
|
1772
|
-
}
|
|
1773
|
-
return urls.map(async (url) => {
|
|
1774
|
-
let batch;
|
|
1775
|
-
let relayUrl;
|
|
1776
|
-
try {
|
|
1777
|
-
const relay = await pool.ensureRelay(url, {
|
|
1778
|
-
signal: opts.signal,
|
|
1779
|
-
timeoutMs: opts.connectTimeoutMs
|
|
1780
|
-
});
|
|
1781
|
-
relayUrl = relay.url;
|
|
1782
|
-
batch = (await relay.fetch([...job.filters], {
|
|
1783
|
-
timeoutMs: opts.timeoutMs,
|
|
1784
|
-
signal: opts.signal
|
|
1785
|
-
})).events;
|
|
1786
|
-
} catch {
|
|
1787
|
-
if (opts.signal?.aborted === true) throw abortReason(opts.signal);
|
|
1788
|
-
return;
|
|
1789
|
-
}
|
|
1790
|
-
for (const event of batch) if (!byId.has(event.id)) byId.set(event.id, event);
|
|
1791
|
-
for (const event of batch) invokeSafely(() => opts.onevent?.(event, relayUrl));
|
|
1792
|
-
});
|
|
1793
|
-
}));
|
|
1794
|
-
return [...byId.values()];
|
|
1795
|
-
}
|
|
1796
|
-
//#endregion
|
|
1797
|
-
//#region src/relay/pool.ts
|
|
1798
|
-
function isIdle(relay) {
|
|
1799
|
-
return relay.status !== RelayStatus.Connecting && relay.subscriptionCount === 0 && relay.inFlightCount === 0;
|
|
1800
|
-
}
|
|
1801
|
-
/** Multi-relay coordinator: connection reuse, cross-relay event dedup, fan-out publish. */
|
|
1802
|
-
var Pool = class {
|
|
1803
|
-
#relays = /* @__PURE__ */ new Map();
|
|
1804
|
-
#opts;
|
|
1805
|
-
#lastActivity = /* @__PURE__ */ new Map();
|
|
1806
|
-
#idleTimeoutMs;
|
|
1807
|
-
#idleTimer;
|
|
1808
|
-
#allowInsecure;
|
|
1809
|
-
#trustedInsecure;
|
|
1810
|
-
#pinned;
|
|
1811
|
-
/** Per-relay timestamps of verification failures inside the sliding window. */
|
|
1812
|
-
#invalidEvents = /* @__PURE__ */ new Map();
|
|
1813
|
-
/** Normalized URL → epoch ms when its suspension lifts. */
|
|
1814
|
-
#suspendedUntil = /* @__PURE__ */ new Map();
|
|
1815
|
-
#resumeTimers = /* @__PURE__ */ new Map();
|
|
1816
|
-
constructor(opts = {}) {
|
|
1817
|
-
this.#opts = opts;
|
|
1818
|
-
this.#allowInsecure = opts.allowInsecure ?? false;
|
|
1819
|
-
this.#trustedInsecure = new Set((opts.trustedInsecureUrls ?? []).map(normalizeURL));
|
|
1820
|
-
this.#pinned = new Set((opts.pinnedUrls ?? []).map(normalizeURL));
|
|
1821
|
-
this.#idleTimeoutMs = opts.idleTimeoutMs ?? 0;
|
|
1822
|
-
if (this.#idleTimeoutMs > 0) this.#idleTimer = setInterval(() => this.cleanIdleRelays(), opts.idleCleanupIntervalMs ?? 3e4);
|
|
1823
|
-
}
|
|
1824
|
-
setAllowInsecure(allow) {
|
|
1825
|
-
this.#allowInsecure = allow;
|
|
1826
|
-
}
|
|
1827
|
-
setTrustedInsecureUrls(urls) {
|
|
1828
|
-
this.#trustedInsecure = new Set(urls.map(normalizeURL));
|
|
1829
|
-
}
|
|
1830
|
-
setPinnedUrls(urls) {
|
|
1831
|
-
this.#pinned = new Set(urls.map(normalizeURL));
|
|
1832
|
-
}
|
|
1833
|
-
/**
|
|
1834
|
-
* Drop cached AUTH rejections and re-fire pending challenges on every pooled relay, so a later
|
|
1835
|
-
* `setSigner` takes effect without reconnect.
|
|
1836
|
-
*/
|
|
1837
|
-
resetAuth() {
|
|
1838
|
-
for (const relay of this.#relays.values()) relay.resetAuth();
|
|
1839
|
-
}
|
|
1840
|
-
cleanIdleRelays() {
|
|
1841
|
-
if (this.#idleTimeoutMs <= 0) return;
|
|
1842
|
-
const now = Date.now();
|
|
1843
|
-
const idle = [];
|
|
1844
|
-
for (const [url, relay] of this.#relays) {
|
|
1845
|
-
if (this.#pinned.has(url) || !isIdle(relay)) continue;
|
|
1846
|
-
const last = this.#lastActivity.get(url) ?? 0;
|
|
1847
|
-
if (!relay.connected || now - last >= this.#idleTimeoutMs) idle.push(url);
|
|
1848
|
-
}
|
|
1849
|
-
for (const url of this.#lastActivity.keys()) if (!this.#relays.has(url)) this.#lastActivity.delete(url);
|
|
1850
|
-
if (idle.length === 0) return;
|
|
1851
|
-
this.close(idle);
|
|
1852
|
-
invokeSafely(() => this.#opts.onIdleRelaysClosed?.(idle));
|
|
1853
|
-
}
|
|
1854
|
-
#touch(url) {
|
|
1855
|
-
this.#lastActivity.set(url, Date.now());
|
|
1856
|
-
}
|
|
1857
|
-
/**
|
|
1858
|
-
* Count one verification failure for `norm`; on the `limit + 1`-th inside `windowMs` suspend the
|
|
1859
|
-
* relay: drop its connection (subscriptions kept), record `until`, and fire `onRelaySuspended`.
|
|
1860
|
-
*/
|
|
1861
|
-
#noteInvalidEvent(norm) {
|
|
1862
|
-
const policy = this.#opts.invalidEventPolicy;
|
|
1863
|
-
if (policy === void 0) return;
|
|
1864
|
-
const now = Date.now();
|
|
1865
|
-
const window = (this.#invalidEvents.get(norm) ?? []).filter((at) => now - at < policy.windowMs);
|
|
1866
|
-
window.push(now);
|
|
1867
|
-
if (window.length <= policy.limit) {
|
|
1868
|
-
this.#invalidEvents.set(norm, window);
|
|
1869
|
-
return;
|
|
1870
|
-
}
|
|
1871
|
-
this.#invalidEvents.delete(norm);
|
|
1872
|
-
if (this.#suspendedUntil.has(norm)) return;
|
|
1873
|
-
const until = now + policy.cooldownMs;
|
|
1874
|
-
this.#suspendedUntil.set(norm, until);
|
|
1875
|
-
this.#relays.get(norm)?.disconnect();
|
|
1876
|
-
const resume = setTimeout(() => {
|
|
1877
|
-
this.#resumeTimers.delete(norm);
|
|
1878
|
-
this.#suspendedUntil.delete(norm);
|
|
1879
|
-
const relay = this.#relays.get(norm);
|
|
1880
|
-
if (relay === void 0 || relay.connected || relay.subscriptionCount === 0) return;
|
|
1881
|
-
(async () => {
|
|
1882
|
-
try {
|
|
1883
|
-
await relay.connect();
|
|
1884
|
-
} catch {}
|
|
1885
|
-
})();
|
|
1886
|
-
}, policy.cooldownMs);
|
|
1887
|
-
this.#resumeTimers.set(norm, resume);
|
|
1888
|
-
invokeSafely(() => this.#opts.onRelaySuspended?.(norm, until));
|
|
1889
|
-
}
|
|
1890
|
-
#rejectInsecure(url, norm) {
|
|
1891
|
-
if (this.#allowInsecure) return;
|
|
1892
|
-
if (!isInsecureRelayUrl(url)) return;
|
|
1893
|
-
if (this.#trustedInsecure.has(norm)) return;
|
|
1894
|
-
throw new RelayConnectionError("insecure relay connection blocked", norm);
|
|
1895
|
-
}
|
|
1896
|
-
#stopIdleCleanup() {
|
|
1897
|
-
if (this.#idleTimer === void 0) return;
|
|
1898
|
-
clearInterval(this.#idleTimer);
|
|
1899
|
-
this.#idleTimer = void 0;
|
|
1900
|
-
}
|
|
1901
|
-
/**
|
|
1902
|
-
* Soft cap: closing the least-recently-used idle non-pinned relay when a new non-pinned relay
|
|
1903
|
-
* would push the count past `maxRelays`. Busy relays are never evicted; with none idle the cap is
|
|
1904
|
-
* exceeded rather than failing.
|
|
1905
|
-
*/
|
|
1906
|
-
#enforceMaxRelays(incoming) {
|
|
1907
|
-
const cap = this.#opts.maxRelays;
|
|
1908
|
-
if (cap === void 0 || this.#pinned.has(incoming)) return;
|
|
1909
|
-
let count = 0;
|
|
1910
|
-
let oldestUrl;
|
|
1911
|
-
let oldestAt = Number.POSITIVE_INFINITY;
|
|
1912
|
-
for (const [url, relay] of this.#relays) {
|
|
1913
|
-
if (this.#pinned.has(url)) continue;
|
|
1914
|
-
count += 1;
|
|
1915
|
-
if (!isIdle(relay)) continue;
|
|
1916
|
-
const at = this.#lastActivity.get(url) ?? 0;
|
|
1917
|
-
if (at < oldestAt) {
|
|
1918
|
-
oldestAt = at;
|
|
1919
|
-
oldestUrl = url;
|
|
1920
|
-
}
|
|
1921
|
-
}
|
|
1922
|
-
if (count >= cap && oldestUrl !== void 0) this.close([oldestUrl]);
|
|
1923
|
-
}
|
|
1924
|
-
async ensureRelay(url, opts) {
|
|
1925
|
-
const norm = normalizeURL(url);
|
|
1926
|
-
this.#rejectInsecure(url, norm);
|
|
1927
|
-
const suspendedUntil = this.#suspendedUntil.get(norm);
|
|
1928
|
-
if (suspendedUntil !== void 0) {
|
|
1929
|
-
if (Date.now() < suspendedUntil) throw new RelaySuspendedError(norm, suspendedUntil);
|
|
1930
|
-
this.#suspendedUntil.delete(norm);
|
|
1931
|
-
}
|
|
1932
|
-
let relay = this.#relays.get(norm);
|
|
1933
|
-
if (!relay) {
|
|
1934
|
-
this.#enforceMaxRelays(norm);
|
|
1935
|
-
const signFn = this.#opts.automaticallyAuth?.(norm) ?? void 0;
|
|
1936
|
-
const created = new Relay(norm, {
|
|
1937
|
-
websocketImplementation: this.#opts.websocketImplementation,
|
|
1938
|
-
verifyEvent: this.#opts.verifyEvent,
|
|
1939
|
-
publishTimeoutMs: this.#opts.publishTimeoutMs,
|
|
1940
|
-
connectTimeoutMs: this.#opts.connectTimeoutMs,
|
|
1941
|
-
enableReconnect: this.#opts.enableReconnect,
|
|
1942
|
-
reconnectBackoffMs: this.#opts.reconnectBackoffMs,
|
|
1943
|
-
enablePing: this.#opts.enablePing,
|
|
1944
|
-
pingIntervalMs: this.#opts.pingIntervalMs,
|
|
1945
|
-
pingTimeoutMs: this.#opts.pingTimeoutMs,
|
|
1946
|
-
authSigner: signFn
|
|
1947
|
-
});
|
|
1948
|
-
created.onclose = () => {
|
|
1949
|
-
this.#relays.delete(norm);
|
|
1950
|
-
this.#lastActivity.delete(norm);
|
|
1951
|
-
};
|
|
1952
|
-
created.oninvalidevent = () => {
|
|
1953
|
-
this.#noteInvalidEvent(norm);
|
|
1954
|
-
};
|
|
1955
|
-
if (signFn) created.onauth = () => {
|
|
1956
|
-
(async () => {
|
|
1957
|
-
try {
|
|
1958
|
-
await created.auth(signFn);
|
|
1959
|
-
} catch {}
|
|
1960
|
-
})();
|
|
1961
|
-
};
|
|
1962
|
-
this.#relays.set(norm, created);
|
|
1963
|
-
relay = created;
|
|
1964
|
-
}
|
|
1965
|
-
this.#touch(norm);
|
|
1966
|
-
if (!relay.connected) try {
|
|
1967
|
-
await relay.connect({
|
|
1968
|
-
signal: opts?.signal,
|
|
1969
|
-
timeoutMs: opts?.timeoutMs ?? this.#opts.connectTimeoutMs
|
|
1970
|
-
});
|
|
1971
|
-
} catch (error) {
|
|
1972
|
-
if (this.#opts.enableReconnect !== true) {
|
|
1973
|
-
this.#relays.delete(norm);
|
|
1974
|
-
this.#lastActivity.delete(norm);
|
|
1975
|
-
}
|
|
1976
|
-
throw error;
|
|
1977
|
-
}
|
|
1978
|
-
return relay;
|
|
1979
|
-
}
|
|
1980
|
-
close(urls) {
|
|
1981
|
-
if (!urls) {
|
|
1982
|
-
this.#stopIdleCleanup();
|
|
1983
|
-
for (const timer of this.#resumeTimers.values()) clearTimeout(timer);
|
|
1984
|
-
this.#resumeTimers.clear();
|
|
1985
|
-
this.#suspendedUntil.clear();
|
|
1986
|
-
this.#invalidEvents.clear();
|
|
1987
|
-
for (const relay of this.#relays.values()) relay.close();
|
|
1988
|
-
this.#relays.clear();
|
|
1989
|
-
this.#lastActivity.clear();
|
|
1990
|
-
return;
|
|
1991
|
-
}
|
|
1992
|
-
for (const url of urls) {
|
|
1993
|
-
const norm = normalizeURL(url);
|
|
1994
|
-
const timer = this.#resumeTimers.get(norm);
|
|
1995
|
-
if (timer !== void 0) {
|
|
1996
|
-
clearTimeout(timer);
|
|
1997
|
-
this.#resumeTimers.delete(norm);
|
|
1998
|
-
}
|
|
1999
|
-
this.#relays.get(norm)?.close();
|
|
2000
|
-
this.#relays.delete(norm);
|
|
2001
|
-
this.#lastActivity.delete(norm);
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
/**
|
|
2005
|
-
* Subscribe across relays. Deduplicates by event id. Returns a closer; callbacks receive every
|
|
2006
|
-
* new event once.
|
|
2007
|
-
*/
|
|
2008
|
-
subscribe(relays, filters, opts = {}) {
|
|
2009
|
-
if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
|
|
2010
|
-
if (opts.id !== void 0) assertSubscriptionId(opts.id);
|
|
2011
|
-
const canonical = canonicalizeFilters(filters);
|
|
2012
|
-
return fanIn(this, [{
|
|
2013
|
-
urls: relays,
|
|
2014
|
-
filters: canonical,
|
|
2015
|
-
id: opts.id
|
|
2016
|
-
}], {
|
|
2017
|
-
onevent: opts.onevent,
|
|
2018
|
-
oneose: opts.oneose,
|
|
2019
|
-
onclose: opts.onclose,
|
|
2020
|
-
signal: opts.signal,
|
|
2021
|
-
eoseTimeoutMs: opts.eoseTimeoutMs,
|
|
2022
|
-
alreadyHaveEvent: opts.alreadyHaveEvent,
|
|
2023
|
-
receivedEvent: opts.receivedEvent,
|
|
2024
|
-
closeOnEose: opts.closeOnEose,
|
|
2025
|
-
connectTimeoutMs: this.#opts.connectTimeoutMs
|
|
2026
|
-
});
|
|
2027
|
-
}
|
|
2028
|
-
/**
|
|
2029
|
-
* One-shot fetch per relay: each entry reports its own events and end reason (`eose`, `closed`,
|
|
2030
|
-
* `timeout`), or `failed` when the connect or REQ itself errored. An abort rejects the whole
|
|
2031
|
-
* call. Events are not deduped across relays — use {@link fetch} for that.
|
|
2032
|
-
*/
|
|
2033
|
-
async fetchEach(relays, filters, opts) {
|
|
2034
|
-
if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
|
|
2035
|
-
throwIfAborted(opts?.signal);
|
|
2036
|
-
const canonical = canonicalizeFilters(filters);
|
|
2037
|
-
return Promise.all(relays.map(async (url) => {
|
|
2038
|
-
try {
|
|
2039
|
-
const relay = await this.ensureRelay(url, {
|
|
2040
|
-
signal: opts?.signal,
|
|
2041
|
-
timeoutMs: this.#opts.connectTimeoutMs
|
|
2042
|
-
});
|
|
2043
|
-
this.#touch(relay.url);
|
|
2044
|
-
const result = await relay.fetch([...canonical], {
|
|
2045
|
-
timeoutMs: opts?.timeoutMs,
|
|
2046
|
-
signal: opts?.signal
|
|
2047
|
-
});
|
|
2048
|
-
return {
|
|
2049
|
-
url: relay.url,
|
|
2050
|
-
events: result.events,
|
|
2051
|
-
end: result.end
|
|
2052
|
-
};
|
|
2053
|
-
} catch (error) {
|
|
2054
|
-
if (opts?.signal?.aborted === true) throw abortReason(opts.signal);
|
|
2055
|
-
return {
|
|
2056
|
-
url,
|
|
2057
|
-
events: [],
|
|
2058
|
-
end: {
|
|
2059
|
-
type: "failed",
|
|
2060
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
2061
|
-
}
|
|
2062
|
-
};
|
|
2063
|
-
}
|
|
2064
|
-
}));
|
|
2065
|
-
}
|
|
2066
|
-
/** Fetch events until each connected relay EOSE or timeout; dedupe by id. */
|
|
2067
|
-
async fetch(relays, filters, opts) {
|
|
2068
|
-
if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
|
|
2069
|
-
return fetchRouted(this, [{
|
|
2070
|
-
urls: relays,
|
|
2071
|
-
filters: canonicalizeFilters(filters)
|
|
2072
|
-
}], {
|
|
2073
|
-
timeoutMs: opts?.timeoutMs,
|
|
2074
|
-
signal: opts?.signal,
|
|
2075
|
-
connectTimeoutMs: this.#opts.connectTimeoutMs,
|
|
2076
|
-
onevent: opts?.onevent
|
|
2077
|
-
});
|
|
2078
|
-
}
|
|
2079
|
-
/** Publish to all listed relays; returns per-relay outcomes. */
|
|
2080
|
-
async publish(relays, event, opts) {
|
|
2081
|
-
return await Promise.all(relays.map(async (url) => {
|
|
2082
|
-
try {
|
|
2083
|
-
const relay = await this.ensureRelay(url, { timeoutMs: this.#opts.connectTimeoutMs });
|
|
2084
|
-
this.#touch(relay.url);
|
|
2085
|
-
const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });
|
|
2086
|
-
return {
|
|
2087
|
-
url: relay.url,
|
|
2088
|
-
result
|
|
2089
|
-
};
|
|
2090
|
-
} catch (error) {
|
|
2091
|
-
return {
|
|
2092
|
-
url,
|
|
2093
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2094
|
-
};
|
|
2095
|
-
}
|
|
2096
|
-
}));
|
|
2097
|
-
}
|
|
2098
|
-
/** First successful publish (Promise.any semantics). */
|
|
2099
|
-
async publishAny(relays, event, opts) {
|
|
2100
|
-
return Promise.any(relays.map(async (url) => {
|
|
2101
|
-
const relay = await this.ensureRelay(url, { timeoutMs: this.#opts.connectTimeoutMs });
|
|
2102
|
-
this.#touch(relay.url);
|
|
2103
|
-
const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });
|
|
2104
|
-
if (!result.ok) throw new RelayPublishError(result.message || "rejected", relay.url);
|
|
2105
|
-
return {
|
|
2106
|
-
url: relay.url,
|
|
2107
|
-
result
|
|
2108
|
-
};
|
|
2109
|
-
}));
|
|
2110
|
-
}
|
|
2111
|
-
/**
|
|
2112
|
-
* NIP-45 COUNT across relays. Per-relay outcomes; failures do not throw. Counts are not summed —
|
|
2113
|
-
* each relay reports independently (may overlap).
|
|
2114
|
-
*/
|
|
2115
|
-
async count(relays, filters, opts) {
|
|
2116
|
-
throwIfAborted(opts?.signal);
|
|
2117
|
-
const canonical = canonicalizeFilters(filters);
|
|
2118
|
-
return await Promise.all(relays.map(async (url) => {
|
|
2119
|
-
try {
|
|
2120
|
-
const relay = await this.ensureRelay(url, {
|
|
2121
|
-
signal: opts?.signal,
|
|
2122
|
-
timeoutMs: this.#opts.connectTimeoutMs
|
|
2123
|
-
});
|
|
2124
|
-
this.#touch(relay.url);
|
|
2125
|
-
const payload = await relay.count(canonical, {
|
|
2126
|
-
timeoutMs: opts?.timeoutMs,
|
|
2127
|
-
signal: opts?.signal
|
|
2128
|
-
});
|
|
2129
|
-
const out = {
|
|
2130
|
-
url: relay.url,
|
|
2131
|
-
count: payload.count
|
|
2132
|
-
};
|
|
2133
|
-
if (payload.approximate !== void 0) out.approximate = payload.approximate;
|
|
2134
|
-
if (payload.hll !== void 0) out.hll = payload.hll;
|
|
2135
|
-
return out;
|
|
2136
|
-
} catch (error) {
|
|
2137
|
-
if (opts?.signal?.aborted === true) throw abortReason(opts.signal);
|
|
2138
|
-
return {
|
|
2139
|
-
url,
|
|
2140
|
-
error: error instanceof Error ? error.message : String(error)
|
|
2141
|
-
};
|
|
2142
|
-
}
|
|
2143
|
-
}));
|
|
2144
|
-
}
|
|
2145
|
-
listRelays() {
|
|
2146
|
-
return [...this.#relays.keys()];
|
|
2147
|
-
}
|
|
2148
|
-
/** Lookup a pooled relay, including disconnected reconnecting entries. */
|
|
2149
|
-
getRelay(url) {
|
|
2150
|
-
try {
|
|
2151
|
-
return this.#relays.get(normalizeURL(url));
|
|
2152
|
-
} catch {
|
|
2153
|
-
return this.#relays.get(url);
|
|
2154
|
-
}
|
|
2155
|
-
}
|
|
2156
|
-
/** Currently connected URLs. Unlike listRelays(), excludes reconnecting/disconnected entries. */
|
|
2157
|
-
connectedUrls() {
|
|
2158
|
-
const urls = [];
|
|
2159
|
-
for (const [url, relay] of this.#relays) if (relay.connected) urls.push(url);
|
|
2160
|
-
return urls;
|
|
2161
|
-
}
|
|
2162
|
-
};
|
|
2163
|
-
//#endregion
|
|
2164
|
-
export { RelayStatus as a, isInsecureRelayUrl as c, RelayClosedError as d, RelayConnectionError as f, RelayTimeoutError as g, RelaySuspendedError as h, Relay as i, getWebSocketImplementation as l, RelayPublishError as m, fanIn as n, Subscription as o, RelayError as p, fetchRouted as r, subscriptionToAsyncIterable as s, Pool as t, useWebSocketImplementation as u };
|
|
1621
|
+
export { getWebSocketImplementation as a, RelayConnectionError as c, RelaySuspendedError as d, RelayTimeoutError as f, subscriptionToAsyncIterable as i, RelayError as l, Relay as n, useWebSocketImplementation as o, RelayStatus as r, RelayClosedError as s, DEFAULT_REQUEST_TIMEOUT_MS as t, RelayPublishError as u };
|
|
2165
1622
|
|
|
2166
|
-
//# sourceMappingURL=
|
|
1623
|
+
//# sourceMappingURL=relay-CvxPiQ_m.mjs.map
|