@qntx/nostr 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/abort-Ba8tDrOI.mjs +38 -0
- package/dist/abort-Ba8tDrOI.mjs.map +1 -0
- package/dist/{builder-BphJa-Y-.d.mts → builder-BWn55Paf.d.mts} +20 -14
- package/dist/{builder-q0VsvnYE.mjs → builder-CyrGKRVP.mjs} +32 -16
- package/dist/builder-CyrGKRVP.mjs.map +1 -0
- package/dist/{client-Bd8B1mYm.mjs → client-5DhgYV7P.mjs} +372 -195
- package/dist/client-5DhgYV7P.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/core.d.mts +8 -8
- package/dist/core.mjs +8 -9
- package/dist/error-BW7re1UN.d.mts +9 -0
- package/dist/error-B__OMBRY.mjs +14 -0
- package/dist/error-B__OMBRY.mjs.map +1 -0
- package/dist/{error-WEFS-R-4.d.mts → error-CR8Urc84.d.mts} +20 -8
- package/dist/{error-CwBxle6N.mjs → error-DrEIM7FO.mjs} +5 -3
- package/dist/error-DrEIM7FO.mjs.map +1 -0
- package/dist/{event-CzoQEFMF.mjs → event-D8Nfoh4K.mjs} +40 -32
- package/dist/event-D8Nfoh4K.mjs.map +1 -0
- package/dist/{event-B5D2jxt_.d.mts → event-fU3Y01PR.d.mts} +23 -20
- package/dist/{filter-BxwjEkZp.mjs → filter-BQl3GlHt.mjs} +42 -35
- package/dist/filter-BQl3GlHt.mjs.map +1 -0
- package/dist/filter-C0IMByE3.d.mts +37 -0
- package/dist/{gossip-C5RVB1UB.d.mts → gossip-BRQwIM_q.d.mts} +25 -23
- package/dist/{gossip-3xkRRCm_.mjs → gossip-oloDMWEj.mjs} +52 -34
- package/dist/gossip-oloDMWEj.mjs.map +1 -0
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/{http-6vevbBlu.d.mts → http-C4HwIu4B.d.mts} +7 -7
- package/dist/{http-D2rhokZo.mjs → http-CuazNRIP.mjs} +10 -6
- package/dist/http-CuazNRIP.mjs.map +1 -0
- package/dist/index-B-CGNDPS.d.mts +207 -0
- package/dist/index-BIhSADwK.d.mts +339 -0
- package/dist/{index-BYMnhRIC.d.mts → index-Ba7bJ7DD.d.mts} +68 -50
- package/dist/{index-DaRyjo7O.d.mts → index-Boe8FshM.d.mts} +13 -7
- package/dist/index-D-TK_KJG.d.mts +84 -0
- package/dist/index-D1bqFHKB.d.mts +188 -0
- package/dist/index.d.mts +22 -20
- package/dist/index.mjs +19 -18
- package/dist/{key-Dn-aX4x4.mjs → key-Bbtys6o7.mjs} +22 -14
- package/dist/key-Bbtys6o7.mjs.map +1 -0
- package/dist/{key-BiGxGLhg.d.mts → key-BlsYhN1V.d.mts} +9 -6
- package/dist/{kind-BLl4cLOc.mjs → kind-Cxwhhkx8.mjs} +2 -1
- package/dist/kind-Cxwhhkx8.mjs.map +1 -0
- package/dist/{loaders-BNQszDAn.mjs → loaders-o6YXB_Ri.mjs} +329 -305
- package/dist/loaders-o6YXB_Ri.mjs.map +1 -0
- package/dist/loaders.d.mts +3 -3
- package/dist/loaders.mjs +2 -2
- package/dist/memory-C2EVx0_O.mjs +46 -0
- package/dist/memory-C2EVx0_O.mjs.map +1 -0
- package/dist/{memory-BCmhZPek.mjs → memory-index-Do1mz0WF.mjs} +212 -82
- package/dist/memory-index-Do1mz0WF.mjs.map +1 -0
- package/dist/{message-C_Uj-Y-c.mjs → message-DcY6Fi9o.mjs} +38 -24
- package/dist/message-DcY6Fi9o.mjs.map +1 -0
- package/dist/{message-DJC-Jz6T.d.mts → message-iGPIqnn0.d.mts} +15 -8
- package/dist/{nip17-BWq-Kfq5.d.mts → nip17-DNEkz8rM.d.mts} +18 -17
- package/dist/{nip19-D7905oIv.d.mts → nip19-BevCJZsp.d.mts} +23 -3
- package/dist/{nip46-C_DoxiS_.d.mts → nip46-DDV4dC56.d.mts} +13 -12
- package/dist/{nip59-DljW_sYh.d.mts → nip59-zQgdLcEr.d.mts} +28 -25
- package/dist/{nip65-Di5hZRuV.d.mts → nip65-DBfh2smt.d.mts} +8 -3
- package/dist/{nip77-ByxlgoQO.d.mts → nip77-DH4RhS5r.d.mts} +9 -8
- package/dist/nips/blossom.d.mts +37 -37
- package/dist/nips/blossom.mjs +67 -52
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.d.mts +2 -3
- package/dist/nips/nip04.mjs +6 -10
- package/dist/nips/nip04.mjs.map +1 -1
- package/dist/nips/nip05.d.mts +37 -43
- package/dist/nips/nip05.mjs +57 -57
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +18 -19
- package/dist/nips/nip10.mjs +76 -61
- package/dist/nips/nip10.mjs.map +1 -1
- package/dist/nips/nip11.d.mts +9 -8
- package/dist/nips/nip11.mjs +14 -11
- package/dist/nips/nip11.mjs.map +1 -1
- package/dist/nips/nip13.d.mts +9 -9
- package/dist/nips/nip13.mjs +15 -17
- package/dist/nips/nip13.mjs.map +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +17 -12
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +1 -1
- package/dist/nips/nip19.mjs +52 -29
- package/dist/nips/nip19.mjs.map +1 -1
- package/dist/nips/nip21.d.mts +5 -6
- package/dist/nips/nip21.mjs +8 -7
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +7 -8
- package/dist/nips/nip27.mjs +26 -25
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.d.mts +5 -6
- package/dist/nips/nip42.mjs +3 -3
- package/dist/nips/nip42.mjs.map +1 -1
- package/dist/nips/nip44.d.mts +24 -7
- package/dist/nips/nip44.mjs +25 -15
- package/dist/nips/nip44.mjs.map +1 -1
- package/dist/nips/nip46.d.mts +1 -1
- package/dist/nips/nip46.mjs +52 -49
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +8 -7
- package/dist/nips/nip49.mjs +16 -12
- package/dist/nips/nip49.mjs.map +1 -1
- package/dist/nips/nip51.d.mts +25 -27
- package/dist/nips/nip51.mjs +47 -36
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.d.mts +12 -13
- package/dist/nips/nip57.mjs +35 -38
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +14 -18
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +11 -8
- package/dist/nips/nip65.mjs.map +1 -1
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +56 -34
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip96.d.mts +21 -12
- package/dist/nips/nip96.mjs +59 -36
- package/dist/nips/nip96.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +12 -12
- package/dist/nips/nip98.mjs +18 -19
- package/dist/nips/nip98.mjs.map +1 -1
- package/dist/pool-B7p_reAm.d.mts +333 -0
- package/dist/{pool-C_j1OmBB.mjs → pool-C2xOcsun.mjs} +792 -568
- package/dist/pool-C2xOcsun.mjs.map +1 -0
- package/dist/reactive-DDlThDSZ.mjs +324 -0
- package/dist/reactive-DDlThDSZ.mjs.map +1 -0
- package/dist/reactive-DFpqtIf_.d.mts +66 -0
- package/dist/relay.d.mts +5 -4
- package/dist/relay.mjs +2 -2
- package/dist/report-CHld4uWj.mjs +32 -0
- package/dist/report-CHld4uWj.mjs.map +1 -0
- package/dist/{signer-CZ6K6FUc.mjs → signer-CKR9EHce.mjs} +107 -72
- package/dist/signer-CKR9EHce.mjs.map +1 -0
- package/dist/signer.d.mts +3 -3
- package/dist/signer.mjs +3 -2
- package/dist/storage-xXxLGCVb.mjs +1376 -0
- package/dist/storage-xXxLGCVb.mjs.map +1 -0
- package/dist/storage.d.mts +4 -4
- package/dist/storage.mjs +5 -4
- package/dist/store.d.mts +2 -0
- package/dist/store.mjs +2 -0
- package/dist/testing.d.mts +135 -0
- package/dist/testing.mjs +963 -0
- package/dist/testing.mjs.map +1 -0
- package/dist/types-2pCUprRU.d.mts +47 -0
- package/dist/types-CRQEk0Oi.d.mts +19 -0
- package/dist/util-LfQirgDk.mjs +113 -0
- package/dist/util-LfQirgDk.mjs.map +1 -0
- package/dist/wasm.d.mts +4 -4
- package/dist/wasm.mjs +54 -55
- package/dist/wasm.mjs.map +1 -1
- package/dist/websocket-CmeqvAkp.d.mts +27 -0
- package/package.json +93 -39
- package/dist/builder-q0VsvnYE.mjs.map +0 -1
- package/dist/client-Bd8B1mYm.mjs.map +0 -1
- package/dist/error-B49rbDzT.d.mts +0 -7
- package/dist/error-CwBxle6N.mjs.map +0 -1
- package/dist/error-DZEs5OuZ.mjs +0 -24
- package/dist/error-DZEs5OuZ.mjs.map +0 -1
- package/dist/event-CzoQEFMF.mjs.map +0 -1
- package/dist/filter-BxwjEkZp.mjs.map +0 -1
- package/dist/filter-DUEKP3q1.d.mts +0 -40
- package/dist/gossip-3xkRRCm_.mjs.map +0 -1
- package/dist/http-D2rhokZo.mjs.map +0 -1
- package/dist/index-C0C4e-Gn.d.mts +0 -55
- package/dist/index-DgOugrpF.d.mts +0 -275
- package/dist/index-EwL6fHLo.d.mts +0 -267
- package/dist/index-k2YhCgsh.d.mts +0 -57
- package/dist/key-Dn-aX4x4.mjs.map +0 -1
- package/dist/kind-BLl4cLOc.mjs.map +0 -1
- package/dist/loaders-BNQszDAn.mjs.map +0 -1
- package/dist/memory-BCmhZPek.mjs.map +0 -1
- package/dist/message-C_Uj-Y-c.mjs.map +0 -1
- package/dist/pool-C_j1OmBB.mjs.map +0 -1
- package/dist/pool-DhRi7l-b.d.mts +0 -297
- package/dist/signer-CZ6K6FUc.mjs.map +0 -1
- package/dist/storage-DOy0iLRE.mjs +0 -870
- package/dist/storage-DOy0iLRE.mjs.map +0 -1
- package/dist/types-BzyADqnw.d.mts +0 -19
- package/dist/types-Dn24TPFs.d.mts +0 -43
- package/dist/util-6tfLQ9JF.mjs +0 -73
- package/dist/util-6tfLQ9JF.mjs.map +0 -1
|
@@ -1,26 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { g as NostrError, h as MessageError, l as normalizeURL, v as WasmVerifyPoisonedError } from "./util-LfQirgDk.mjs";
|
|
2
|
+
import { s as verifyEvent } from "./key-Bbtys6o7.mjs";
|
|
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-DcY6Fi9o.mjs";
|
|
5
|
+
import { t as NoSignerError } from "./error-B__OMBRY.mjs";
|
|
6
|
+
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
6
7
|
import { isAuthRequired, makeAuthEvent } from "./nips/nip42.mjs";
|
|
8
|
+
import { n as raceSignal, r as throwIfAborted, t as abortReason } from "./abort-Ba8tDrOI.mjs";
|
|
7
9
|
import { Nip77Error, runNegSession } from "./nips/nip77.mjs";
|
|
8
10
|
//#region src/relay/error.ts
|
|
11
|
+
/** Base class for relay errors; carries the relay URL when known. */
|
|
9
12
|
var RelayError = class extends NostrError {
|
|
13
|
+
name = "RelayError";
|
|
10
14
|
url;
|
|
11
|
-
constructor(message, url) {
|
|
12
|
-
super(url ? `${message} (${url})` : message);
|
|
15
|
+
constructor(message, url, options) {
|
|
16
|
+
super(url !== void 0 && url !== "" ? `${message} (${url})` : message, options);
|
|
13
17
|
this.url = url;
|
|
14
18
|
}
|
|
15
19
|
};
|
|
16
|
-
|
|
17
|
-
var
|
|
18
|
-
|
|
20
|
+
/** A relay connection attempt or socket failed. */
|
|
21
|
+
var RelayConnectionError = class extends RelayError {
|
|
22
|
+
name = "RelayConnectionError";
|
|
23
|
+
};
|
|
24
|
+
/** A relay answered an EVENT with `ok: false`. */
|
|
25
|
+
var RelayPublishError = class extends RelayError {
|
|
26
|
+
name = "RelayPublishError";
|
|
27
|
+
};
|
|
28
|
+
/** A subscription was closed by the relay (`CLOSED`) or the socket dropped. */
|
|
29
|
+
var RelayClosedError = class extends RelayError {
|
|
30
|
+
name = "RelayClosedError";
|
|
31
|
+
};
|
|
32
|
+
/** A relay operation exceeded its deadline. */
|
|
33
|
+
var RelayTimeoutError = class extends RelayError {
|
|
34
|
+
name = "RelayTimeoutError";
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A relay was suspended for delivering more events that fail id/signature verification than its
|
|
38
|
+
* {@link PoolOptions.invalidEventPolicy} allows. Carries `until`, the epoch-ms time when the relay
|
|
39
|
+
* may connect again.
|
|
40
|
+
*/
|
|
41
|
+
var RelaySuspendedError = class extends RelayError {
|
|
42
|
+
name = "RelaySuspendedError";
|
|
43
|
+
/** Epoch milliseconds when the suspension lifts. */
|
|
44
|
+
until;
|
|
45
|
+
constructor(url, until) {
|
|
46
|
+
super("relay suspended for delivering invalid events", url);
|
|
47
|
+
this.until = until;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
19
50
|
//#endregion
|
|
20
51
|
//#region src/relay/websocket.ts
|
|
21
52
|
let impl;
|
|
22
53
|
try {
|
|
23
|
-
if (
|
|
54
|
+
if (globalThis.WebSocket !== void 0) impl = globalThis.WebSocket;
|
|
24
55
|
} catch {}
|
|
25
56
|
/** Inject a WebSocket implementation (required on Node unless undici/global is present). */
|
|
26
57
|
function useWebSocketImplementation(websocketImplementation) {
|
|
@@ -33,8 +64,8 @@ function getWebSocketImplementation() {
|
|
|
33
64
|
//#endregion
|
|
34
65
|
//#region src/relay/url.ts
|
|
35
66
|
/**
|
|
36
|
-
* True for `ws:` / `http:` URLs that are not `.onion`.
|
|
37
|
-
*
|
|
67
|
+
* True for `ws:` / `http:` URLs that are not `.onion`. Local-network `ws://` is still insecure;
|
|
68
|
+
* allow those via a trusted set.
|
|
38
69
|
*/
|
|
39
70
|
function isInsecureRelayUrl(url) {
|
|
40
71
|
try {
|
|
@@ -46,9 +77,211 @@ function isInsecureRelayUrl(url) {
|
|
|
46
77
|
}
|
|
47
78
|
}
|
|
48
79
|
//#endregion
|
|
80
|
+
//#region src/relay/neg-session.ts
|
|
81
|
+
function createNegSession() {
|
|
82
|
+
return {
|
|
83
|
+
queue: [],
|
|
84
|
+
waiter: void 0,
|
|
85
|
+
error: void 0
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function pushNegMsg(session, hex) {
|
|
89
|
+
if (session.waiter) {
|
|
90
|
+
const { waiter } = session;
|
|
91
|
+
session.waiter = void 0;
|
|
92
|
+
waiter.resolve(hex);
|
|
93
|
+
} else session.queue.push(hex);
|
|
94
|
+
}
|
|
95
|
+
function failNegSession(session, err) {
|
|
96
|
+
session.error = err;
|
|
97
|
+
if (session.waiter) {
|
|
98
|
+
const { waiter } = session;
|
|
99
|
+
session.waiter = void 0;
|
|
100
|
+
waiter.reject(err);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function failNegErr(session, reason) {
|
|
104
|
+
failNegSession(session, new Nip77Error(reason));
|
|
105
|
+
}
|
|
106
|
+
/** Wire queue/timeout/abort around `runNegSession`. Not a second session class. */
|
|
107
|
+
async function runWiredNegSession(opts) {
|
|
108
|
+
const { session, storage, filter, id, timeoutMs, signal, send, url } = opts;
|
|
109
|
+
const deadline = Date.now() + timeoutMs;
|
|
110
|
+
const timedOut = () => new RelayTimeoutError("negentropy timed out", url);
|
|
111
|
+
const remainingMs = () => deadline - Date.now();
|
|
112
|
+
const next = async () => {
|
|
113
|
+
if (session.error !== void 0) throw session.error;
|
|
114
|
+
if (remainingMs() <= 0) throw timedOut();
|
|
115
|
+
const queued = session.queue.shift();
|
|
116
|
+
if (queued !== void 0) return queued;
|
|
117
|
+
return new Promise((resolve, reject) => {
|
|
118
|
+
const timer = setTimeout(() => {
|
|
119
|
+
session.waiter = void 0;
|
|
120
|
+
reject(timedOut());
|
|
121
|
+
}, remainingMs());
|
|
122
|
+
const fail = (err) => {
|
|
123
|
+
clearTimeout(timer);
|
|
124
|
+
session.waiter = void 0;
|
|
125
|
+
reject(err);
|
|
126
|
+
};
|
|
127
|
+
const onAbort = () => {
|
|
128
|
+
if (signal) fail(abortReason(signal));
|
|
129
|
+
};
|
|
130
|
+
session.waiter = {
|
|
131
|
+
resolve: (hex) => {
|
|
132
|
+
clearTimeout(timer);
|
|
133
|
+
signal?.removeEventListener("abort", onAbort);
|
|
134
|
+
resolve(hex);
|
|
135
|
+
},
|
|
136
|
+
reject: (err) => fail(err)
|
|
137
|
+
};
|
|
138
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
return runNegSession({
|
|
142
|
+
storage,
|
|
143
|
+
openingSend: (hex) => {
|
|
144
|
+
send([
|
|
145
|
+
"NEG-OPEN",
|
|
146
|
+
id,
|
|
147
|
+
filter,
|
|
148
|
+
hex
|
|
149
|
+
]);
|
|
150
|
+
},
|
|
151
|
+
msgSend: (hex) => {
|
|
152
|
+
send([
|
|
153
|
+
"NEG-MSG",
|
|
154
|
+
id,
|
|
155
|
+
hex
|
|
156
|
+
]);
|
|
157
|
+
},
|
|
158
|
+
next
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
const PING_DUMMY_FILTER = {
|
|
162
|
+
ids: ["a".repeat(64)],
|
|
163
|
+
limit: 0
|
|
164
|
+
};
|
|
165
|
+
/** Browser WebSocket and `ws` both use 1 for OPEN. */
|
|
166
|
+
const WS_OPEN = 1;
|
|
167
|
+
/** Node `ws` delivers `pong` on the EventEmitter, not via addEventListener. */
|
|
168
|
+
function canNativePing(ws) {
|
|
169
|
+
return typeof ws.ping === "function" && (typeof ws.once === "function" || typeof ws.on === "function");
|
|
170
|
+
}
|
|
171
|
+
/** REQ-dummy or native ping/pong keepalive. Does not import class Relay. */
|
|
172
|
+
var PingLoop = class {
|
|
173
|
+
#opts;
|
|
174
|
+
#timer;
|
|
175
|
+
#waiters = /* @__PURE__ */ new Map();
|
|
176
|
+
#gen = 0;
|
|
177
|
+
#nativePing;
|
|
178
|
+
constructor(opts) {
|
|
179
|
+
this.#opts = opts;
|
|
180
|
+
}
|
|
181
|
+
start() {
|
|
182
|
+
this.stop();
|
|
183
|
+
this.#timer = setInterval(() => {
|
|
184
|
+
this.#pingpong();
|
|
185
|
+
}, this.#opts.pingIntervalMs);
|
|
186
|
+
}
|
|
187
|
+
stop() {
|
|
188
|
+
if (this.#timer !== void 0) {
|
|
189
|
+
clearInterval(this.#timer);
|
|
190
|
+
this.#timer = void 0;
|
|
191
|
+
}
|
|
192
|
+
this.#abortCurrentPing();
|
|
193
|
+
}
|
|
194
|
+
hasWaiter(id) {
|
|
195
|
+
return this.#waiters.has(id);
|
|
196
|
+
}
|
|
197
|
+
finishDummyPing(id) {
|
|
198
|
+
const waiter = this.#waiters.get(id);
|
|
199
|
+
if (!waiter) return false;
|
|
200
|
+
this.#waiters.delete(id);
|
|
201
|
+
waiter.resolve(true);
|
|
202
|
+
try {
|
|
203
|
+
this.#opts.send(["CLOSE", id]);
|
|
204
|
+
} catch {}
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
#abortCurrentPing() {
|
|
208
|
+
this.#gen += 1;
|
|
209
|
+
const native = this.#nativePing;
|
|
210
|
+
this.#nativePing = void 0;
|
|
211
|
+
native?.abort();
|
|
212
|
+
for (const waiter of this.#waiters.values()) waiter.resolve(false);
|
|
213
|
+
this.#waiters.clear();
|
|
214
|
+
}
|
|
215
|
+
async #pingpong() {
|
|
216
|
+
const ws = this.#opts.getWs();
|
|
217
|
+
if (!ws || ws.readyState !== WS_OPEN) return;
|
|
218
|
+
if (this.#nativePing !== void 0 || this.#waiters.size > 0) return;
|
|
219
|
+
const gen = this.#gen;
|
|
220
|
+
const ping = canNativePing(ws) ? this.#waitForNativePing(ws) : this.#waitForDummyPing();
|
|
221
|
+
const ok = await new Promise((resolve) => {
|
|
222
|
+
let settled = false;
|
|
223
|
+
const done = (alive) => {
|
|
224
|
+
if (settled) return;
|
|
225
|
+
settled = true;
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
resolve(alive);
|
|
228
|
+
};
|
|
229
|
+
const timer = setTimeout(() => {
|
|
230
|
+
if (this.#nativePing !== void 0 || this.#waiters.size > 0) done(false);
|
|
231
|
+
}, this.#opts.pingTimeoutMs);
|
|
232
|
+
(async () => {
|
|
233
|
+
done(await ping);
|
|
234
|
+
})();
|
|
235
|
+
});
|
|
236
|
+
if (gen !== this.#gen) return;
|
|
237
|
+
if (!ok) {
|
|
238
|
+
this.#abortCurrentPing();
|
|
239
|
+
this.#opts.closeWs();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async #waitForNativePing(ws) {
|
|
243
|
+
return new Promise((resolve) => {
|
|
244
|
+
let settled = false;
|
|
245
|
+
const finish = (alive) => {
|
|
246
|
+
if (settled) return;
|
|
247
|
+
settled = true;
|
|
248
|
+
this.#nativePing = void 0;
|
|
249
|
+
ws.off?.("pong", onPong);
|
|
250
|
+
resolve(alive);
|
|
251
|
+
};
|
|
252
|
+
const onPong = () => finish(true);
|
|
253
|
+
this.#nativePing = { abort: () => finish(false) };
|
|
254
|
+
if (typeof ws.on === "function" && typeof ws.off === "function") ws.on("pong", onPong);
|
|
255
|
+
else if (typeof ws.once === "function") ws.once("pong", onPong);
|
|
256
|
+
else ws.on?.("pong", onPong);
|
|
257
|
+
try {
|
|
258
|
+
if (typeof ws.ping === "function") ws.ping();
|
|
259
|
+
else finish(false);
|
|
260
|
+
} catch {
|
|
261
|
+
finish(false);
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
async #waitForDummyPing() {
|
|
266
|
+
return new Promise((resolve) => {
|
|
267
|
+
const id = this.#opts.nextSubId("__ping__");
|
|
268
|
+
this.#waiters.set(id, { resolve });
|
|
269
|
+
try {
|
|
270
|
+
this.#opts.send([
|
|
271
|
+
"REQ",
|
|
272
|
+
id,
|
|
273
|
+
PING_DUMMY_FILTER
|
|
274
|
+
]);
|
|
275
|
+
} catch {
|
|
276
|
+
this.#waiters.delete(id);
|
|
277
|
+
resolve(false);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
//#endregion
|
|
49
283
|
//#region src/relay/subscription.ts
|
|
50
284
|
var Subscription = class {
|
|
51
|
-
sendClose;
|
|
52
285
|
id;
|
|
53
286
|
filters;
|
|
54
287
|
handlers;
|
|
@@ -61,9 +294,10 @@ var Subscription = class {
|
|
|
61
294
|
lastCreatedAt;
|
|
62
295
|
/** Event ids at `lastCreatedAt` (same-second reconnect dedup). Not all seen ids. */
|
|
63
296
|
idsAtWatermark = /* @__PURE__ */ new Set();
|
|
297
|
+
#sendClose;
|
|
64
298
|
#abort;
|
|
65
299
|
constructor(filters, opts, sendClose) {
|
|
66
|
-
this
|
|
300
|
+
this.#sendClose = sendClose;
|
|
67
301
|
this.id = createSubscriptionId(opts.id);
|
|
68
302
|
this.filters = filters;
|
|
69
303
|
this.closeOnEose = opts.closeOnEose === true;
|
|
@@ -87,8 +321,8 @@ var Subscription = class {
|
|
|
87
321
|
if (this.closed) return;
|
|
88
322
|
this.closed = true;
|
|
89
323
|
this.#abort?.();
|
|
90
|
-
this
|
|
91
|
-
this.handlers.onclose?.(reason);
|
|
324
|
+
this.#sendClose(this.id);
|
|
325
|
+
invokeSafely(() => this.handlers.onclose?.(reason));
|
|
92
326
|
}
|
|
93
327
|
/** Advance the reconnect watermark after a verified EVENT. */
|
|
94
328
|
noteVerified(event) {
|
|
@@ -101,8 +335,8 @@ var Subscription = class {
|
|
|
101
335
|
if (event.created_at === this.lastCreatedAt) this.idsAtWatermark.add(event.id);
|
|
102
336
|
}
|
|
103
337
|
/**
|
|
104
|
-
* Filters for re-REQ. Original `filters` stay unchanged.
|
|
105
|
-
*
|
|
338
|
+
* Filters for re-REQ. Original `filters` stay unchanged. NIP-01 `since` is inclusive — never
|
|
339
|
+
* `lastCreatedAt + 1`.
|
|
106
340
|
*/
|
|
107
341
|
replayFilters() {
|
|
108
342
|
const since = this.lastCreatedAt;
|
|
@@ -120,65 +354,62 @@ function subscriptionToAsyncIterable(start, opts) {
|
|
|
120
354
|
let error;
|
|
121
355
|
let wake;
|
|
122
356
|
let closer;
|
|
357
|
+
let localClose = false;
|
|
123
358
|
const notify = () => {
|
|
124
359
|
wake?.();
|
|
125
360
|
wake = void 0;
|
|
126
361
|
};
|
|
362
|
+
const waitForWake = async () => new Promise((resolve) => {
|
|
363
|
+
wake = resolve;
|
|
364
|
+
});
|
|
365
|
+
const closeLocal = (reason) => {
|
|
366
|
+
localClose = true;
|
|
367
|
+
closer?.close(reason);
|
|
368
|
+
done = true;
|
|
369
|
+
notify();
|
|
370
|
+
};
|
|
127
371
|
closer = start({
|
|
128
372
|
onevent(event) {
|
|
129
373
|
queue.push(event);
|
|
130
374
|
notify();
|
|
131
375
|
},
|
|
132
376
|
oneose() {
|
|
133
|
-
if (opts?.includeEose === false)
|
|
134
|
-
done = true;
|
|
135
|
-
closer?.close("eose");
|
|
136
|
-
notify();
|
|
137
|
-
}
|
|
377
|
+
if (opts?.includeEose === false) closeLocal("eose");
|
|
138
378
|
},
|
|
139
379
|
onclose(reason) {
|
|
140
|
-
if (
|
|
380
|
+
if (opts?.signal?.aborted === true) localClose = true;
|
|
381
|
+
if (!localClose && reason) error = new RelayClosedError(reason);
|
|
141
382
|
done = true;
|
|
142
383
|
notify();
|
|
143
384
|
}
|
|
144
385
|
});
|
|
145
386
|
if (opts?.signal) {
|
|
146
|
-
if (opts.signal.aborted)
|
|
147
|
-
|
|
148
|
-
done = true;
|
|
149
|
-
} else opts.signal.addEventListener("abort", () => {
|
|
150
|
-
closer?.close("aborted");
|
|
151
|
-
done = true;
|
|
152
|
-
notify();
|
|
153
|
-
}, { once: true });
|
|
387
|
+
if (opts.signal.aborted) closeLocal("aborted");
|
|
388
|
+
else opts.signal.addEventListener("abort", () => closeLocal("aborted"), { once: true });
|
|
154
389
|
}
|
|
155
390
|
return {
|
|
156
391
|
close(reason) {
|
|
157
|
-
|
|
158
|
-
done = true;
|
|
159
|
-
notify();
|
|
392
|
+
closeLocal(reason ?? "closed by client");
|
|
160
393
|
},
|
|
161
394
|
[Symbol.asyncIterator]() {
|
|
162
395
|
return {
|
|
163
396
|
async next() {
|
|
164
397
|
while (true) {
|
|
165
|
-
|
|
166
|
-
|
|
398
|
+
const value = queue.shift();
|
|
399
|
+
if (value !== void 0) return {
|
|
400
|
+
value,
|
|
167
401
|
done: false
|
|
168
402
|
};
|
|
169
|
-
if (error) throw error;
|
|
403
|
+
if (error !== void 0) throw error;
|
|
170
404
|
if (done) return {
|
|
171
405
|
value: void 0,
|
|
172
406
|
done: true
|
|
173
407
|
};
|
|
174
|
-
await
|
|
175
|
-
wake = resolve;
|
|
176
|
-
});
|
|
408
|
+
await waitForWake();
|
|
177
409
|
}
|
|
178
410
|
},
|
|
179
411
|
async return() {
|
|
180
|
-
|
|
181
|
-
done = true;
|
|
412
|
+
closeLocal("iterator returned");
|
|
182
413
|
return {
|
|
183
414
|
value: void 0,
|
|
184
415
|
done: true
|
|
@@ -190,18 +421,6 @@ function subscriptionToAsyncIterable(start, opts) {
|
|
|
190
421
|
}
|
|
191
422
|
//#endregion
|
|
192
423
|
//#region src/relay/subscribe.ts
|
|
193
|
-
function captureListenerError(errors, fn) {
|
|
194
|
-
try {
|
|
195
|
-
fn();
|
|
196
|
-
} catch (err) {
|
|
197
|
-
errors.push(err);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
function flushListenerErrors(errors) {
|
|
201
|
-
if (errors.length === 0) return;
|
|
202
|
-
if (errors.length === 1) throw errors[0];
|
|
203
|
-
throw new AggregateError(errors);
|
|
204
|
-
}
|
|
205
424
|
function openExclusive(ctx, filters, opts) {
|
|
206
425
|
const sub = new Subscription(filters, opts, (id) => {
|
|
207
426
|
ctx.subs.delete(id);
|
|
@@ -267,7 +486,7 @@ function subscribeLive(ctx, filters, opts) {
|
|
|
267
486
|
if (group.sub.eosed) queueMicrotask(() => {
|
|
268
487
|
if (handle.closed || handle.eosed) return;
|
|
269
488
|
handle.eosed = true;
|
|
270
|
-
handle.handlers.oneose?.();
|
|
489
|
+
invokeSafely(() => handle.handlers.oneose?.());
|
|
271
490
|
});
|
|
272
491
|
return handle;
|
|
273
492
|
}
|
|
@@ -290,70 +509,54 @@ function detachLive(ctx, fp, handle) {
|
|
|
290
509
|
function endLiveGroup(ctx, fp, opts) {
|
|
291
510
|
const group = ctx.liveByFp.get(fp);
|
|
292
511
|
if (!group) return;
|
|
293
|
-
const id = group.sub
|
|
512
|
+
const { id } = group.sub;
|
|
294
513
|
const remaining = [...group.attachments];
|
|
295
514
|
group.attachments.clear();
|
|
296
515
|
forgetLiveGroup(ctx, group);
|
|
297
516
|
if (opts.sendClose) try {
|
|
298
517
|
if (ctx.connected()) ctx.send(["CLOSE", id]);
|
|
299
518
|
} catch {}
|
|
300
|
-
const
|
|
301
|
-
for (const att of remaining) captureListenerError(errors, () => {
|
|
519
|
+
for (const att of remaining) invokeSafely(() => {
|
|
302
520
|
att.close(opts.reason);
|
|
303
521
|
});
|
|
304
|
-
flushListenerErrors(errors);
|
|
305
522
|
}
|
|
306
523
|
function deliverLiveEvent(ctx, group, event) {
|
|
307
|
-
const sub = group
|
|
524
|
+
const { sub } = group;
|
|
308
525
|
const attachments = [...group.attachments];
|
|
309
|
-
const
|
|
310
|
-
for (const att of attachments) captureListenerError(errors, () => {
|
|
526
|
+
for (const att of attachments) invokeSafely(() => {
|
|
311
527
|
att.handlers.receivedEvent?.(event.id);
|
|
312
528
|
});
|
|
313
|
-
if (sub.idsAtWatermark.has(event.id))
|
|
314
|
-
flushListenerErrors(errors);
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
529
|
+
if (sub.idsAtWatermark.has(event.id)) return;
|
|
317
530
|
const recipients = [];
|
|
318
531
|
for (const att of attachments) {
|
|
319
532
|
if (att.closed) continue;
|
|
320
533
|
let skip = false;
|
|
321
|
-
|
|
534
|
+
invokeSafely(() => {
|
|
322
535
|
skip = Boolean(att.handlers.alreadyHaveEvent?.(event.id));
|
|
323
536
|
});
|
|
324
537
|
if (!skip) recipients.push(att);
|
|
325
538
|
}
|
|
326
|
-
if (recipients.length === 0)
|
|
327
|
-
|
|
328
|
-
return;
|
|
329
|
-
}
|
|
330
|
-
if (!ctx.acceptEvent(event)) {
|
|
331
|
-
flushListenerErrors(errors);
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
539
|
+
if (recipients.length === 0) return;
|
|
540
|
+
if (!ctx.acceptEvent(event)) return;
|
|
334
541
|
sub.noteVerified(event);
|
|
335
542
|
for (const att of recipients) att.noteVerified(event);
|
|
336
|
-
for (const att of recipients)
|
|
543
|
+
for (const att of recipients) invokeSafely(() => {
|
|
337
544
|
att.handlers.onevent?.(event);
|
|
338
545
|
});
|
|
339
|
-
flushListenerErrors(errors);
|
|
340
546
|
}
|
|
341
547
|
function deliverLiveEose(group) {
|
|
342
|
-
const attachments =
|
|
343
|
-
const errors = [];
|
|
548
|
+
const attachments = [...group.attachments];
|
|
344
549
|
for (const att of attachments) {
|
|
345
550
|
if (att.closed || att.eosed) continue;
|
|
346
551
|
att.eosed = true;
|
|
347
|
-
|
|
552
|
+
invokeSafely(() => {
|
|
348
553
|
att.handlers.oneose?.();
|
|
349
554
|
});
|
|
350
555
|
}
|
|
351
|
-
flushListenerErrors(errors);
|
|
352
556
|
}
|
|
353
557
|
function closeAllSubscriptions(ctx, reason) {
|
|
354
|
-
const
|
|
355
|
-
const fps
|
|
356
|
-
for (const fp of fps) captureListenerError(errors, () => {
|
|
558
|
+
const fps = [...ctx.liveByFp.keys()];
|
|
559
|
+
for (const fp of fps) invokeSafely(() => {
|
|
357
560
|
endLiveGroup(ctx, fp, {
|
|
358
561
|
sendClose: false,
|
|
359
562
|
reason
|
|
@@ -361,14 +564,13 @@ function closeAllSubscriptions(ctx, reason) {
|
|
|
361
564
|
});
|
|
362
565
|
for (const sub of ctx.subs.values()) if (!sub.closed) {
|
|
363
566
|
sub.closed = true;
|
|
364
|
-
|
|
567
|
+
invokeSafely(() => {
|
|
365
568
|
sub.handlers.onclose?.(reason);
|
|
366
569
|
});
|
|
367
570
|
}
|
|
368
571
|
ctx.subs.clear();
|
|
369
572
|
ctx.liveByFp.clear();
|
|
370
573
|
ctx.liveBySubId.clear();
|
|
371
|
-
flushListenerErrors(errors);
|
|
372
574
|
}
|
|
373
575
|
function dropSubscription(ctx, sub, reason) {
|
|
374
576
|
const group = ctx.liveBySubId.get(sub.id);
|
|
@@ -381,7 +583,9 @@ function dropSubscription(ctx, sub, reason) {
|
|
|
381
583
|
}
|
|
382
584
|
ctx.subs.delete(sub.id);
|
|
383
585
|
sub.closed = true;
|
|
384
|
-
|
|
586
|
+
invokeSafely(() => {
|
|
587
|
+
sub.handlers.onclose?.(reason);
|
|
588
|
+
});
|
|
385
589
|
}
|
|
386
590
|
function onSubEvent(ctx, subId, event) {
|
|
387
591
|
const sub = ctx.subs.get(subId);
|
|
@@ -391,302 +595,121 @@ function onSubEvent(ctx, subId, event) {
|
|
|
391
595
|
deliverLiveEvent(ctx, group, event);
|
|
392
596
|
return;
|
|
393
597
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
if (
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
if (!
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (group) for (const att of group.attachments) att.eosed = false;
|
|
420
|
-
try {
|
|
421
|
-
ctx.send([
|
|
422
|
-
"REQ",
|
|
423
|
-
sub.id,
|
|
424
|
-
...sub.replayFilters()
|
|
425
|
-
]);
|
|
426
|
-
} catch {
|
|
427
|
-
return false;
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
return true;
|
|
431
|
-
}
|
|
432
|
-
function streamFilters(subscribe, filters, opts) {
|
|
433
|
-
return subscriptionToAsyncIterable((handlers) => subscribe(filters, {
|
|
434
|
-
...handlers,
|
|
435
|
-
id: opts?.id,
|
|
436
|
-
signal: opts?.signal
|
|
437
|
-
}), { signal: opts?.signal });
|
|
438
|
-
}
|
|
439
|
-
async function fetchFilters(subscribe, filters, opts) {
|
|
440
|
-
const events = [];
|
|
441
|
-
const seen = /* @__PURE__ */ new Set();
|
|
442
|
-
await new Promise((resolve, reject) => {
|
|
443
|
-
let settled = false;
|
|
444
|
-
const done = (err) => {
|
|
445
|
-
if (settled) return;
|
|
446
|
-
settled = true;
|
|
447
|
-
clearTimeout(timer);
|
|
448
|
-
sub.close(err ? err.message : "fetch complete");
|
|
449
|
-
if (err) reject(err);
|
|
450
|
-
else resolve();
|
|
451
|
-
};
|
|
452
|
-
const timer = setTimeout(() => done(), opts.timeoutMs);
|
|
453
|
-
const sub = subscribe(filters, {
|
|
454
|
-
id: opts.id,
|
|
455
|
-
signal: opts.signal,
|
|
456
|
-
closeOnEose: true,
|
|
457
|
-
onevent(event) {
|
|
458
|
-
if (seen.has(event.id)) return;
|
|
459
|
-
seen.add(event.id);
|
|
460
|
-
events.push(event);
|
|
461
|
-
},
|
|
462
|
-
oneose() {
|
|
463
|
-
done();
|
|
464
|
-
},
|
|
465
|
-
onclose() {
|
|
466
|
-
if (!settled) done();
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
opts.signal?.addEventListener("abort", () => done(new RelayConnectionError("fetch aborted", opts.url)), { once: true });
|
|
470
|
-
});
|
|
471
|
-
return events;
|
|
472
|
-
}
|
|
473
|
-
function armEoseTimeout(sub, eoseTimeoutMs) {
|
|
474
|
-
const timer = setTimeout(() => {
|
|
475
|
-
if (sub.eosed || sub.closed) return;
|
|
476
|
-
sub.eosed = true;
|
|
477
|
-
sub.handlers.oneose?.();
|
|
478
|
-
}, eoseTimeoutMs);
|
|
479
|
-
const prevClose = sub.handlers.onclose;
|
|
480
|
-
sub.handlers.onclose = (reason) => {
|
|
481
|
-
clearTimeout(timer);
|
|
482
|
-
prevClose?.(reason);
|
|
483
|
-
};
|
|
484
|
-
const prevEose = sub.handlers.oneose;
|
|
485
|
-
sub.handlers.oneose = () => {
|
|
486
|
-
clearTimeout(timer);
|
|
487
|
-
prevEose?.();
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
//#endregion
|
|
491
|
-
//#region src/relay/neg-session.ts
|
|
492
|
-
function createNegSession() {
|
|
493
|
-
return {
|
|
494
|
-
queue: [],
|
|
495
|
-
waiter: void 0,
|
|
496
|
-
error: void 0
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
function pushNegMsg(session, hex) {
|
|
500
|
-
if (session.waiter) {
|
|
501
|
-
const waiter = session.waiter;
|
|
502
|
-
session.waiter = void 0;
|
|
503
|
-
waiter.resolve(hex);
|
|
504
|
-
} else session.queue.push(hex);
|
|
505
|
-
}
|
|
506
|
-
function failNegSession(session, err) {
|
|
507
|
-
session.error = err;
|
|
508
|
-
if (session.waiter) {
|
|
509
|
-
const waiter = session.waiter;
|
|
510
|
-
session.waiter = void 0;
|
|
511
|
-
waiter.reject(err);
|
|
512
|
-
}
|
|
513
|
-
}
|
|
514
|
-
function failNegErr(session, reason) {
|
|
515
|
-
failNegSession(session, new Nip77Error(reason));
|
|
516
|
-
}
|
|
517
|
-
/** Wire queue/timeout/abort around `runNegSession`. Not a second session class. */
|
|
518
|
-
async function runWiredNegSession(opts) {
|
|
519
|
-
const { session, storage, filter, id, timeoutMs, signal, send, url } = opts;
|
|
520
|
-
const deadline = Date.now() + timeoutMs;
|
|
521
|
-
const timedOut = () => new RelayPublishError("negentropy timed out", url);
|
|
522
|
-
const remainingMs = () => deadline - Date.now();
|
|
523
|
-
const next = () => {
|
|
524
|
-
if (session.error) return Promise.reject(session.error);
|
|
525
|
-
if (remainingMs() <= 0) return Promise.reject(timedOut());
|
|
526
|
-
const queued = session.queue.shift();
|
|
527
|
-
if (queued !== void 0) return Promise.resolve(queued);
|
|
528
|
-
return new Promise((resolve, reject) => {
|
|
529
|
-
const timer = setTimeout(() => {
|
|
530
|
-
session.waiter = void 0;
|
|
531
|
-
reject(timedOut());
|
|
532
|
-
}, remainingMs());
|
|
533
|
-
const fail = (err) => {
|
|
534
|
-
clearTimeout(timer);
|
|
535
|
-
session.waiter = void 0;
|
|
536
|
-
reject(err);
|
|
537
|
-
};
|
|
538
|
-
const onAbort = () => fail(new RelayConnectionError("negentropy aborted", url));
|
|
539
|
-
session.waiter = {
|
|
540
|
-
resolve: (hex) => {
|
|
541
|
-
clearTimeout(timer);
|
|
542
|
-
signal?.removeEventListener("abort", onAbort);
|
|
543
|
-
resolve(hex);
|
|
544
|
-
},
|
|
545
|
-
reject: (err) => fail(err)
|
|
546
|
-
};
|
|
547
|
-
signal?.addEventListener("abort", onAbort, { once: true });
|
|
548
|
-
});
|
|
549
|
-
};
|
|
550
|
-
return await runNegSession({
|
|
551
|
-
storage,
|
|
552
|
-
openingSend: (hex) => {
|
|
553
|
-
send([
|
|
554
|
-
"NEG-OPEN",
|
|
555
|
-
id,
|
|
556
|
-
filter,
|
|
557
|
-
hex
|
|
558
|
-
]);
|
|
559
|
-
},
|
|
560
|
-
msgSend: (hex) => {
|
|
561
|
-
send([
|
|
562
|
-
"NEG-MSG",
|
|
563
|
-
id,
|
|
564
|
-
hex
|
|
565
|
-
]);
|
|
566
|
-
},
|
|
567
|
-
next
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
const PING_DUMMY_FILTER = {
|
|
571
|
-
ids: ["a".repeat(64)],
|
|
572
|
-
limit: 0
|
|
573
|
-
};
|
|
574
|
-
/** Browser WebSocket and `ws` both use 1 for OPEN. */
|
|
575
|
-
const WS_OPEN = 1;
|
|
576
|
-
/** node `ws` delivers `pong` on the EventEmitter, not via addEventListener. */
|
|
577
|
-
function canNativePing(ws) {
|
|
578
|
-
return typeof ws.ping === "function" && (typeof ws.once === "function" || typeof ws.on === "function");
|
|
579
|
-
}
|
|
580
|
-
/** REQ-dummy or native ping/pong keepalive. Does not import class Relay. */
|
|
581
|
-
var PingLoop = class {
|
|
582
|
-
#opts;
|
|
583
|
-
#timer;
|
|
584
|
-
#waiters = /* @__PURE__ */ new Map();
|
|
585
|
-
#gen = 0;
|
|
586
|
-
#nativePing;
|
|
587
|
-
constructor(opts) {
|
|
588
|
-
this.#opts = opts;
|
|
589
|
-
}
|
|
590
|
-
start() {
|
|
591
|
-
this.stop();
|
|
592
|
-
this.#timer = setInterval(() => {
|
|
593
|
-
this.#pingpong();
|
|
594
|
-
}, this.#opts.pingIntervalMs);
|
|
595
|
-
}
|
|
596
|
-
stop() {
|
|
597
|
-
if (this.#timer !== void 0) {
|
|
598
|
-
clearInterval(this.#timer);
|
|
599
|
-
this.#timer = void 0;
|
|
600
|
-
}
|
|
601
|
-
this.#abortCurrentPing();
|
|
602
|
-
}
|
|
603
|
-
hasWaiter(id) {
|
|
604
|
-
return this.#waiters.has(id);
|
|
605
|
-
}
|
|
606
|
-
finishDummyPing(id) {
|
|
607
|
-
const waiter = this.#waiters.get(id);
|
|
608
|
-
if (!waiter) return false;
|
|
609
|
-
this.#waiters.delete(id);
|
|
610
|
-
waiter.resolve(true);
|
|
611
|
-
try {
|
|
612
|
-
this.#opts.send(["CLOSE", id]);
|
|
613
|
-
} catch {}
|
|
614
|
-
return true;
|
|
615
|
-
}
|
|
616
|
-
#abortCurrentPing() {
|
|
617
|
-
this.#gen += 1;
|
|
618
|
-
const native = this.#nativePing;
|
|
619
|
-
this.#nativePing = void 0;
|
|
620
|
-
native?.abort();
|
|
621
|
-
for (const waiter of this.#waiters.values()) waiter.resolve(false);
|
|
622
|
-
this.#waiters.clear();
|
|
623
|
-
}
|
|
624
|
-
async #pingpong() {
|
|
625
|
-
const ws = this.#opts.getWs();
|
|
626
|
-
if (!ws || ws.readyState !== WS_OPEN) return;
|
|
627
|
-
if (this.#nativePing || this.#waiters.size > 0) return;
|
|
628
|
-
const gen = this.#gen;
|
|
629
|
-
const ping = canNativePing(ws) ? this.#waitForNativePing(ws) : this.#waitForDummyPing();
|
|
630
|
-
const ok = await new Promise((resolve) => {
|
|
631
|
-
let settled = false;
|
|
632
|
-
const done = (alive) => {
|
|
633
|
-
if (settled) return;
|
|
634
|
-
settled = true;
|
|
635
|
-
clearTimeout(timer);
|
|
636
|
-
resolve(alive);
|
|
637
|
-
};
|
|
638
|
-
const timer = setTimeout(() => {
|
|
639
|
-
if (this.#nativePing || this.#waiters.size > 0) done(false);
|
|
640
|
-
}, this.#opts.pingTimeoutMs);
|
|
641
|
-
ping.then(done);
|
|
598
|
+
invokeSafely(() => {
|
|
599
|
+
sub.handlers.receivedEvent?.(event.id);
|
|
600
|
+
});
|
|
601
|
+
if (sub.idsAtWatermark.has(event.id)) return;
|
|
602
|
+
let have = false;
|
|
603
|
+
invokeSafely(() => {
|
|
604
|
+
have = Boolean(sub.handlers.alreadyHaveEvent?.(event.id));
|
|
605
|
+
});
|
|
606
|
+
if (have) return;
|
|
607
|
+
if (!ctx.acceptEvent(event)) return;
|
|
608
|
+
sub.noteVerified(event);
|
|
609
|
+
invokeSafely(() => {
|
|
610
|
+
sub.handlers.onevent?.(event);
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
function onSubEose(ctx, subId) {
|
|
614
|
+
const sub = ctx.subs.get(subId);
|
|
615
|
+
if (!sub || sub.closed) return;
|
|
616
|
+
if (sub.eosed) return;
|
|
617
|
+
sub.eosed = true;
|
|
618
|
+
const group = ctx.liveBySubId.get(subId);
|
|
619
|
+
if (group) deliverLiveEose(group);
|
|
620
|
+
else {
|
|
621
|
+
invokeSafely(() => {
|
|
622
|
+
sub.handlers.oneose?.();
|
|
642
623
|
});
|
|
643
|
-
if (
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
624
|
+
if (sub.closeOnEose) sub.close("eose");
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
function resubscribeAll(ctx) {
|
|
628
|
+
for (const sub of ctx.subs.values()) {
|
|
629
|
+
if (sub.closed) continue;
|
|
630
|
+
sub.eosed = false;
|
|
631
|
+
sub.authRetried = false;
|
|
632
|
+
const group = ctx.liveBySubId.get(sub.id);
|
|
633
|
+
if (group) for (const att of group.attachments) att.eosed = false;
|
|
634
|
+
try {
|
|
635
|
+
ctx.send([
|
|
636
|
+
"REQ",
|
|
637
|
+
sub.id,
|
|
638
|
+
...sub.replayFilters()
|
|
639
|
+
]);
|
|
640
|
+
} catch {
|
|
641
|
+
return false;
|
|
647
642
|
}
|
|
648
643
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
644
|
+
return true;
|
|
645
|
+
}
|
|
646
|
+
function streamFilters(subscribe, filters, opts) {
|
|
647
|
+
return subscriptionToAsyncIterable((handlers) => subscribe(filters, {
|
|
648
|
+
...handlers,
|
|
649
|
+
id: opts?.id,
|
|
650
|
+
signal: opts?.signal
|
|
651
|
+
}), { signal: opts?.signal });
|
|
652
|
+
}
|
|
653
|
+
async function fetchFilters(subscribe, filters, opts) {
|
|
654
|
+
throwIfAborted(opts.signal);
|
|
655
|
+
const events = [];
|
|
656
|
+
const seen = /* @__PURE__ */ new Set();
|
|
657
|
+
await new Promise((resolve, reject) => {
|
|
658
|
+
let settled = false;
|
|
659
|
+
const done = (err) => {
|
|
660
|
+
if (settled) return;
|
|
661
|
+
settled = true;
|
|
662
|
+
clearTimeout(timer);
|
|
663
|
+
let reason = "aborted";
|
|
664
|
+
if (err instanceof Error) reason = err.message;
|
|
665
|
+
else if (err === void 0) reason = "fetch complete";
|
|
666
|
+
sub.close(reason);
|
|
667
|
+
if (err === void 0) resolve();
|
|
668
|
+
else reject(err);
|
|
669
|
+
};
|
|
670
|
+
const timer = setTimeout(() => done(), opts.timeoutMs);
|
|
671
|
+
const sub = subscribe(filters, {
|
|
672
|
+
id: opts.id,
|
|
673
|
+
signal: opts.signal,
|
|
674
|
+
closeOnEose: true,
|
|
675
|
+
onevent(event) {
|
|
676
|
+
if (seen.has(event.id)) return;
|
|
677
|
+
seen.add(event.id);
|
|
678
|
+
events.push(event);
|
|
679
|
+
},
|
|
680
|
+
oneose() {
|
|
681
|
+
done();
|
|
682
|
+
},
|
|
683
|
+
onclose() {
|
|
684
|
+
if (!settled) done(opts.signal?.aborted === true ? abortReason(opts.signal) : void 0);
|
|
668
685
|
}
|
|
669
686
|
});
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
]);
|
|
681
|
-
} catch {
|
|
682
|
-
this.#waiters.delete(id);
|
|
683
|
-
resolve(false);
|
|
684
|
-
}
|
|
687
|
+
opts.signal?.addEventListener("abort", () => done(opts.signal ? abortReason(opts.signal) : void 0), { once: true });
|
|
688
|
+
});
|
|
689
|
+
return events;
|
|
690
|
+
}
|
|
691
|
+
function armEoseTimeout(sub, eoseTimeoutMs) {
|
|
692
|
+
const timer = setTimeout(() => {
|
|
693
|
+
if (sub.eosed || sub.closed) return;
|
|
694
|
+
sub.eosed = true;
|
|
695
|
+
invokeSafely(() => {
|
|
696
|
+
sub.handlers.oneose?.();
|
|
685
697
|
});
|
|
686
|
-
}
|
|
687
|
-
|
|
698
|
+
}, eoseTimeoutMs);
|
|
699
|
+
const prevClose = sub.handlers.onclose;
|
|
700
|
+
sub.handlers.onclose = (reason) => {
|
|
701
|
+
clearTimeout(timer);
|
|
702
|
+
prevClose?.(reason);
|
|
703
|
+
};
|
|
704
|
+
const prevEose = sub.handlers.oneose;
|
|
705
|
+
sub.handlers.oneose = () => {
|
|
706
|
+
clearTimeout(timer);
|
|
707
|
+
prevEose?.();
|
|
708
|
+
};
|
|
709
|
+
}
|
|
688
710
|
//#endregion
|
|
689
711
|
//#region src/relay/relay.ts
|
|
712
|
+
/** Relay lifecycle states. */
|
|
690
713
|
const RelayStatus = {
|
|
691
714
|
Initialized: "initialized",
|
|
692
715
|
Connecting: "connecting",
|
|
@@ -703,9 +726,10 @@ const DEFAULT_BACKOFF = [
|
|
|
703
726
|
3e4,
|
|
704
727
|
6e4
|
|
705
728
|
];
|
|
729
|
+
const noop = () => {};
|
|
706
730
|
/**
|
|
707
|
-
* Single-relay NIP-01 client.
|
|
708
|
-
*
|
|
731
|
+
* Single-relay NIP-01 client. Connection lifecycle, REQ/CLOSE, EVENT publish ACK, AUTH, COUNT,
|
|
732
|
+
* NIP-77, optional reconnect.
|
|
709
733
|
*/
|
|
710
734
|
var Relay = class Relay {
|
|
711
735
|
url;
|
|
@@ -734,6 +758,10 @@ var Relay = class Relay {
|
|
|
734
758
|
#serial = 0;
|
|
735
759
|
#challenge;
|
|
736
760
|
#authedChallenge;
|
|
761
|
+
/** Challenge value already answered on this connection; duplicates are not re-signed. */
|
|
762
|
+
#answeredChallenge;
|
|
763
|
+
/** Settled OK verdict for `#answeredChallenge`; set only once the relay replies. */
|
|
764
|
+
#answeredResult;
|
|
737
765
|
#authPromise;
|
|
738
766
|
#authSigner;
|
|
739
767
|
#intentionalClose = false;
|
|
@@ -744,12 +772,17 @@ var Relay = class Relay {
|
|
|
744
772
|
#deathHandled = false;
|
|
745
773
|
#enablePing;
|
|
746
774
|
#ping;
|
|
747
|
-
onnotice
|
|
748
|
-
onclose
|
|
775
|
+
onnotice;
|
|
776
|
+
onclose;
|
|
749
777
|
/** Fired when the relay sends a NIP-42 AUTH challenge. */
|
|
750
|
-
onauth
|
|
778
|
+
onauth;
|
|
751
779
|
/** Fired after a successful reconnect (not the initial connect). */
|
|
752
|
-
onreconnect
|
|
780
|
+
onreconnect;
|
|
781
|
+
/**
|
|
782
|
+
* Fired when a delivered EVENT fails id/signature verification, just before the event is dropped.
|
|
783
|
+
* A poisoned verifier dropping events does not fire it.
|
|
784
|
+
*/
|
|
785
|
+
oninvalidevent;
|
|
753
786
|
constructor(url, opts = {}) {
|
|
754
787
|
this.url = normalizeURL(url);
|
|
755
788
|
this.#WS = opts.websocketImplementation ?? getWebSocketImplementation();
|
|
@@ -805,6 +838,10 @@ var Relay = class Relay {
|
|
|
805
838
|
get subscriptionCount() {
|
|
806
839
|
return this.#subs.size;
|
|
807
840
|
}
|
|
841
|
+
/** One-shot requests still awaiting a relay reply (EVENT ACK, COUNT, NEG). */
|
|
842
|
+
get inFlightCount() {
|
|
843
|
+
return this.#publishes.size + this.#counts.size + this.#neg.size;
|
|
844
|
+
}
|
|
808
845
|
static async connect(url, opts) {
|
|
809
846
|
const relay = new Relay(url, opts);
|
|
810
847
|
await relay.connect({ signal: opts?.signal });
|
|
@@ -812,7 +849,11 @@ var Relay = class Relay {
|
|
|
812
849
|
}
|
|
813
850
|
async connect(opts) {
|
|
814
851
|
if (this.#connected) return;
|
|
815
|
-
if (this.#connecting)
|
|
852
|
+
if (this.#connecting) {
|
|
853
|
+
await raceSignal(this.#connecting, opts?.signal);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
throwIfAborted(opts?.signal);
|
|
816
857
|
const gen = ++this.#gen;
|
|
817
858
|
this.#intentionalClose = false;
|
|
818
859
|
this.#skipReconnect = false;
|
|
@@ -821,84 +862,64 @@ var Relay = class Relay {
|
|
|
821
862
|
this.#status = RelayStatus.Connecting;
|
|
822
863
|
const timeoutMs = opts?.timeoutMs ?? this.#connectTimeoutMs;
|
|
823
864
|
const isReconnect = this.#reconnectAttempts > 0;
|
|
824
|
-
let resolveConnect =
|
|
825
|
-
let rejectConnect =
|
|
865
|
+
let resolveConnect = noop;
|
|
866
|
+
let rejectConnect = noop;
|
|
826
867
|
let settled = false;
|
|
827
|
-
let timer;
|
|
828
868
|
let ws;
|
|
829
|
-
let handlers;
|
|
830
869
|
const connecting = new Promise((resolve, reject) => {
|
|
831
870
|
resolveConnect = resolve;
|
|
832
871
|
rejectConnect = reject;
|
|
833
872
|
});
|
|
834
873
|
const release = () => {
|
|
835
|
-
if (
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
if (this.#socketHandlers === handlers) this.#socketHandlers = void 0;
|
|
844
|
-
}
|
|
874
|
+
if (ws === void 0) return;
|
|
875
|
+
try {
|
|
876
|
+
ws.removeEventListener("open", handlers.onOpen);
|
|
877
|
+
ws.removeEventListener("error", handlers.onError);
|
|
878
|
+
ws.removeEventListener("close", handlers.onClose);
|
|
879
|
+
ws.removeEventListener("message", handlers.onMessage);
|
|
880
|
+
} catch {}
|
|
881
|
+
if (this.#socketHandlers === handlers) this.#socketHandlers = void 0;
|
|
845
882
|
try {
|
|
846
883
|
if (ws.readyState !== this.#WS.CLOSED && ws.readyState !== this.#WS.CLOSING) ws.close();
|
|
847
884
|
} catch {}
|
|
848
885
|
if (this.#ws === ws) this.#ws = void 0;
|
|
849
886
|
};
|
|
850
|
-
const onAbort = () => {
|
|
851
|
-
if (gen !== this.#gen) {
|
|
852
|
-
release();
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
this.#intentionalClose = true;
|
|
856
|
-
this.#skipReconnect = true;
|
|
857
|
-
release();
|
|
858
|
-
finish(new RelayConnectionError("connection aborted", this.url));
|
|
859
|
-
if (gen === this.#gen) this.#handleSocketDeath("connection aborted", {
|
|
860
|
-
fromConnectAttempt: true,
|
|
861
|
-
gen
|
|
862
|
-
});
|
|
863
|
-
};
|
|
864
887
|
const finish = (err) => {
|
|
865
888
|
if (settled) return;
|
|
866
889
|
settled = true;
|
|
867
|
-
|
|
890
|
+
clearTimeout(timer);
|
|
868
891
|
if (this.#connectTimer === timer) this.#connectTimer = void 0;
|
|
869
|
-
opts?.signal?.removeEventListener("abort", onAbort);
|
|
870
892
|
if (this.#connectFinish === finish) this.#connectFinish = void 0;
|
|
871
893
|
if (this.#connecting === connecting) this.#connecting = void 0;
|
|
872
|
-
if (err
|
|
873
|
-
else
|
|
894
|
+
if (err === void 0) resolveConnect();
|
|
895
|
+
else rejectConnect(err);
|
|
874
896
|
};
|
|
875
897
|
this.#connecting = connecting;
|
|
876
898
|
this.#connectFinish = finish;
|
|
877
|
-
timer = setTimeout(() => {
|
|
899
|
+
const timer = setTimeout(() => {
|
|
878
900
|
if (gen !== this.#gen) {
|
|
879
901
|
release();
|
|
880
902
|
return;
|
|
881
903
|
}
|
|
882
904
|
if (!isReconnect && !this.#enableReconnect) this.#skipReconnect = true;
|
|
883
905
|
release();
|
|
884
|
-
finish(new
|
|
906
|
+
finish(new RelayTimeoutError("connection timed out", this.url));
|
|
885
907
|
if (gen === this.#gen) this.#handleSocketDeath("connection timed out", {
|
|
886
908
|
fromConnectAttempt: true,
|
|
887
909
|
gen
|
|
888
910
|
});
|
|
889
911
|
}, timeoutMs);
|
|
890
912
|
this.#connectTimer = timer;
|
|
891
|
-
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
892
913
|
try {
|
|
893
914
|
ws = new this.#WS(this.url);
|
|
894
|
-
} catch (
|
|
915
|
+
} catch (error) {
|
|
895
916
|
if (!isReconnect && !this.#enableReconnect) this.#skipReconnect = true;
|
|
896
|
-
finish(
|
|
917
|
+
finish(error);
|
|
897
918
|
if (gen === this.#gen) this.#handleSocketDeath("connection failed", {
|
|
898
919
|
fromConnectAttempt: true,
|
|
899
920
|
gen
|
|
900
921
|
});
|
|
901
|
-
await connecting;
|
|
922
|
+
await raceSignal(connecting, opts?.signal);
|
|
902
923
|
return;
|
|
903
924
|
}
|
|
904
925
|
this.#ws = ws;
|
|
@@ -914,6 +935,8 @@ var Relay = class Relay {
|
|
|
914
935
|
this.#challenge = void 0;
|
|
915
936
|
this.#authPromise = void 0;
|
|
916
937
|
this.#authedChallenge = void 0;
|
|
938
|
+
this.#answeredChallenge = void 0;
|
|
939
|
+
this.#answeredResult = void 0;
|
|
917
940
|
if (!resubscribeAll(this.#live)) {
|
|
918
941
|
this.#connected = false;
|
|
919
942
|
this.#status = RelayStatus.Disconnected;
|
|
@@ -926,7 +949,7 @@ var Relay = class Relay {
|
|
|
926
949
|
this.#reconnectAttempts = 0;
|
|
927
950
|
if (this.#enablePing) this.#ping.start();
|
|
928
951
|
else this.#ping.stop();
|
|
929
|
-
if (wasReconnect) this.onreconnect?.();
|
|
952
|
+
if (wasReconnect) invokeSafely(() => this.onreconnect?.());
|
|
930
953
|
finish();
|
|
931
954
|
};
|
|
932
955
|
const onError = () => {
|
|
@@ -964,7 +987,7 @@ var Relay = class Relay {
|
|
|
964
987
|
const data = typeof ev === "object" && ev !== null && "data" in ev ? ev.data : ev;
|
|
965
988
|
this.#onMessage(String(data));
|
|
966
989
|
};
|
|
967
|
-
handlers = {
|
|
990
|
+
const handlers = {
|
|
968
991
|
onOpen,
|
|
969
992
|
onError,
|
|
970
993
|
onClose,
|
|
@@ -976,7 +999,7 @@ var Relay = class Relay {
|
|
|
976
999
|
ws.addEventListener("error", onError);
|
|
977
1000
|
ws.addEventListener("close", onClose);
|
|
978
1001
|
ws.addEventListener("message", onMessage);
|
|
979
|
-
await connecting;
|
|
1002
|
+
await raceSignal(connecting, opts?.signal);
|
|
980
1003
|
}
|
|
981
1004
|
#detachSocketHandlers() {
|
|
982
1005
|
const h = this.#socketHandlers;
|
|
@@ -1011,8 +1034,32 @@ var Relay = class Relay {
|
|
|
1011
1034
|
this.#detachSocketHandlers();
|
|
1012
1035
|
this.#teardownSocket();
|
|
1013
1036
|
this.#connected = false;
|
|
1014
|
-
this.onclose?.();
|
|
1037
|
+
invokeSafely(() => this.onclose?.());
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
/**
|
|
1041
|
+
* Sever the socket while keeping every subscription: unlike {@link close}, open REQ state is
|
|
1042
|
+
* preserved and reconnect stays suppressed — the next {@link connect} re-subscribes all of them.
|
|
1043
|
+
* {@link Pool} uses this to suspend a relay without tearing down its live subscriptions.
|
|
1044
|
+
*/
|
|
1045
|
+
disconnect() {
|
|
1046
|
+
this.#gen += 1;
|
|
1047
|
+
this.#status = RelayStatus.Disconnected;
|
|
1048
|
+
this.#intentionalClose = true;
|
|
1049
|
+
this.#skipReconnect = true;
|
|
1050
|
+
this.#clearReconnectTimer();
|
|
1051
|
+
if (this.#connectTimer !== void 0) {
|
|
1052
|
+
clearTimeout(this.#connectTimer);
|
|
1053
|
+
this.#connectTimer = void 0;
|
|
1015
1054
|
}
|
|
1055
|
+
this.#ping.stop();
|
|
1056
|
+
this.#connectFinish?.(new RelayClosedError("relay disconnected", this.url));
|
|
1057
|
+
this.#rejectPublishes(new RelayClosedError("relay disconnected", this.url));
|
|
1058
|
+
this.#rejectCounts(new RelayClosedError("relay disconnected", this.url));
|
|
1059
|
+
this.#rejectNeg(new RelayClosedError("relay disconnected", this.url));
|
|
1060
|
+
this.#detachSocketHandlers();
|
|
1061
|
+
this.#teardownSocket();
|
|
1062
|
+
this.#connected = false;
|
|
1016
1063
|
}
|
|
1017
1064
|
#teardownSocket() {
|
|
1018
1065
|
try {
|
|
@@ -1044,9 +1091,7 @@ var Relay = class Relay {
|
|
|
1044
1091
|
for (const session of this.#neg.values()) failNegSession(session, err);
|
|
1045
1092
|
this.#neg.clear();
|
|
1046
1093
|
}
|
|
1047
|
-
/**
|
|
1048
|
-
* Unexpected socket death. Keep subscriptions if reconnecting.
|
|
1049
|
-
*/
|
|
1094
|
+
/** Unexpected socket death. Keep subscriptions if reconnecting. */
|
|
1050
1095
|
#handleSocketDeath(reason, opts) {
|
|
1051
1096
|
if (opts.gen !== this.#gen) return;
|
|
1052
1097
|
if (this.#deathHandled) return;
|
|
@@ -1064,9 +1109,9 @@ var Relay = class Relay {
|
|
|
1064
1109
|
return;
|
|
1065
1110
|
}
|
|
1066
1111
|
if (!this.#intentionalClose) this.#status = RelayStatus.Disconnected;
|
|
1067
|
-
if (
|
|
1112
|
+
if (opts.fromConnectAttempt !== true || this.#subs.size > 0) {
|
|
1068
1113
|
closeAllSubscriptions(this.#live, reason);
|
|
1069
|
-
if (!this.#intentionalClose) this.onclose?.();
|
|
1114
|
+
if (!this.#intentionalClose) invokeSafely(() => this.onclose?.());
|
|
1070
1115
|
}
|
|
1071
1116
|
this.#status = RelayStatus.Closed;
|
|
1072
1117
|
}
|
|
@@ -1078,9 +1123,13 @@ var Relay = class Relay {
|
|
|
1078
1123
|
this.#reconnectTimer = setTimeout(() => {
|
|
1079
1124
|
this.#reconnectTimer = void 0;
|
|
1080
1125
|
if (this.#intentionalClose || this.#connected) return;
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1126
|
+
(async () => {
|
|
1127
|
+
try {
|
|
1128
|
+
await this.connect();
|
|
1129
|
+
} catch {
|
|
1130
|
+
if (this.#enableReconnect && !this.#intentionalClose && !this.#skipReconnect && this.#subs.size > 0 && !this.#connected) this.#scheduleReconnect();
|
|
1131
|
+
}
|
|
1132
|
+
})();
|
|
1084
1133
|
}, delay);
|
|
1085
1134
|
}
|
|
1086
1135
|
#send(message) {
|
|
@@ -1135,7 +1184,7 @@ var Relay = class Relay {
|
|
|
1135
1184
|
const [, eventId, ok, message] = msg;
|
|
1136
1185
|
const waiter = this.#publishes.get(eventId);
|
|
1137
1186
|
if (!waiter) return;
|
|
1138
|
-
if (!ok && isAuthRequired(message) && waiter.event &&
|
|
1187
|
+
if (!ok && isAuthRequired(message) && waiter.event !== void 0 && waiter.authRetried !== true && this.#authSigner) {
|
|
1139
1188
|
waiter.authRetried = true;
|
|
1140
1189
|
this.#authThenRepublish(waiter, eventId, message);
|
|
1141
1190
|
return;
|
|
@@ -1169,14 +1218,23 @@ var Relay = class Relay {
|
|
|
1169
1218
|
failNegErr(session, reason);
|
|
1170
1219
|
break;
|
|
1171
1220
|
}
|
|
1172
|
-
case "NOTICE":
|
|
1173
|
-
|
|
1221
|
+
case "NOTICE": {
|
|
1222
|
+
const [, notice] = msg;
|
|
1223
|
+
invokeSafely(() => this.onnotice?.(notice));
|
|
1224
|
+
break;
|
|
1225
|
+
}
|
|
1226
|
+
case "AUTH": {
|
|
1227
|
+
const [, authChallenge] = msg;
|
|
1228
|
+
if (authChallenge !== this.#challenge) {
|
|
1229
|
+
this.#authPromise = void 0;
|
|
1230
|
+
this.#authedChallenge = void 0;
|
|
1231
|
+
this.#answeredChallenge = void 0;
|
|
1232
|
+
this.#answeredResult = void 0;
|
|
1233
|
+
}
|
|
1234
|
+
this.#challenge = authChallenge;
|
|
1235
|
+
invokeSafely(() => this.onauth?.(authChallenge));
|
|
1174
1236
|
break;
|
|
1175
|
-
|
|
1176
|
-
this.#challenge = msg[1];
|
|
1177
|
-
this.#authPromise = void 0;
|
|
1178
|
-
this.#authedChallenge = void 0;
|
|
1179
|
-
this.onauth?.(msg[1]);
|
|
1237
|
+
}
|
|
1180
1238
|
}
|
|
1181
1239
|
}
|
|
1182
1240
|
/** Low-level REQ with callbacks. Survives reconnect when enableReconnect is on. */
|
|
@@ -1190,28 +1248,28 @@ var Relay = class Relay {
|
|
|
1190
1248
|
#acceptEvent(event) {
|
|
1191
1249
|
if (this.#verifyDead) return false;
|
|
1192
1250
|
try {
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1251
|
+
const ok = this.#verify(event);
|
|
1252
|
+
if (!ok) invokeSafely(() => this.oninvalidevent?.());
|
|
1253
|
+
return ok;
|
|
1254
|
+
} catch (error) {
|
|
1255
|
+
if (error instanceof WasmVerifyPoisonedError || error instanceof WebAssembly.RuntimeError) {
|
|
1196
1256
|
this.#verifyDead = true;
|
|
1197
|
-
this.onnotice?.("verify-poisoned: wasm instance aborted");
|
|
1257
|
+
invokeSafely(() => this.onnotice?.("verify-poisoned: wasm instance aborted"));
|
|
1198
1258
|
return false;
|
|
1199
1259
|
}
|
|
1200
|
-
throw
|
|
1260
|
+
throw error;
|
|
1201
1261
|
}
|
|
1202
1262
|
}
|
|
1203
1263
|
/** AsyncIterable of events for filters until the subscription is closed. */
|
|
1204
1264
|
stream(filters, opts) {
|
|
1205
1265
|
return streamFilters((f, o) => this.subscribe(f, o), filters, opts);
|
|
1206
1266
|
}
|
|
1207
|
-
/**
|
|
1208
|
-
* One-shot query: collect events until EOSE or timeout, then close.
|
|
1209
|
-
*/
|
|
1267
|
+
/** One-shot query: collect events until EOSE or timeout, then close. */
|
|
1210
1268
|
async fetch(filters, opts) {
|
|
1211
1269
|
if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
|
|
1212
|
-
|
|
1270
|
+
const canonical = canonicalizeFilters(filters);
|
|
1213
1271
|
if (!this.#connected) await this.connect({ signal: opts?.signal });
|
|
1214
|
-
return fetchFilters((f, o) => this.subscribe(f, o),
|
|
1272
|
+
return fetchFilters((f, o) => this.subscribe(f, o), canonical, {
|
|
1215
1273
|
timeoutMs: opts?.timeoutMs ?? 4400,
|
|
1216
1274
|
signal: opts?.signal,
|
|
1217
1275
|
id: opts?.id,
|
|
@@ -1225,7 +1283,7 @@ var Relay = class Relay {
|
|
|
1225
1283
|
return await new Promise((resolve, reject) => {
|
|
1226
1284
|
const timer = setTimeout(() => {
|
|
1227
1285
|
this.#publishes.delete(event.id);
|
|
1228
|
-
reject(new
|
|
1286
|
+
reject(new RelayTimeoutError("publish timed out", this.url));
|
|
1229
1287
|
}, timeoutMs);
|
|
1230
1288
|
this.#publishes.set(event.id, {
|
|
1231
1289
|
resolve,
|
|
@@ -1236,25 +1294,25 @@ var Relay = class Relay {
|
|
|
1236
1294
|
});
|
|
1237
1295
|
try {
|
|
1238
1296
|
this.#send(["EVENT", event]);
|
|
1239
|
-
} catch (
|
|
1297
|
+
} catch (error) {
|
|
1240
1298
|
clearTimeout(timer);
|
|
1241
1299
|
this.#publishes.delete(event.id);
|
|
1242
|
-
reject(
|
|
1300
|
+
reject(error instanceof Error ? error : new RelayPublishError("publish failed", this.url));
|
|
1243
1301
|
}
|
|
1244
1302
|
});
|
|
1245
1303
|
}
|
|
1246
1304
|
/**
|
|
1247
|
-
* NIP-45 COUNT: ask the relay how many events match `filters`.
|
|
1248
|
-
*
|
|
1305
|
+
* NIP-45 COUNT: ask the relay how many events match `filters`. Resolves with the COUNT payload
|
|
1306
|
+
* (count / optional approximate / optional hll).
|
|
1249
1307
|
*/
|
|
1250
1308
|
async count(filters, opts) {
|
|
1251
1309
|
if (!this.#connected) throw new RelayClosedError("not connected", this.url);
|
|
1252
1310
|
if (filters.length === 0) throw new RelayError("COUNT requires at least one filter", this.url);
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
const id = opts?.id
|
|
1311
|
+
throwIfAborted(opts?.signal);
|
|
1312
|
+
const canonical = canonicalizeFilters(filters);
|
|
1313
|
+
const id = opts?.id === void 0 ? this.nextSubId("count") : createSubscriptionId(opts.id);
|
|
1256
1314
|
const timeoutMs = opts?.timeoutMs ?? this.#publishTimeoutMs;
|
|
1257
|
-
return
|
|
1315
|
+
return new Promise((resolve, reject) => {
|
|
1258
1316
|
const cleanup = () => {
|
|
1259
1317
|
opts?.signal?.removeEventListener("abort", onAbort);
|
|
1260
1318
|
};
|
|
@@ -1274,13 +1332,15 @@ var Relay = class Relay {
|
|
|
1274
1332
|
reject(err);
|
|
1275
1333
|
},
|
|
1276
1334
|
timer: void 0,
|
|
1277
|
-
filters: [...
|
|
1335
|
+
filters: [...canonical],
|
|
1278
1336
|
authRetried: false,
|
|
1279
1337
|
timeoutMs
|
|
1280
1338
|
};
|
|
1281
|
-
const onAbort = () =>
|
|
1339
|
+
const onAbort = () => {
|
|
1340
|
+
if (opts?.signal) waiter.reject(abortReason(opts.signal));
|
|
1341
|
+
};
|
|
1282
1342
|
waiter.timer = setTimeout(() => {
|
|
1283
|
-
waiter.reject(new
|
|
1343
|
+
waiter.reject(new RelayTimeoutError("count timed out", this.url));
|
|
1284
1344
|
}, timeoutMs);
|
|
1285
1345
|
this.#counts.set(id, waiter);
|
|
1286
1346
|
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
@@ -1288,41 +1348,56 @@ var Relay = class Relay {
|
|
|
1288
1348
|
this.#send([
|
|
1289
1349
|
"COUNT",
|
|
1290
1350
|
id,
|
|
1291
|
-
...
|
|
1351
|
+
...canonical
|
|
1292
1352
|
]);
|
|
1293
|
-
} catch (
|
|
1294
|
-
waiter.reject(
|
|
1353
|
+
} catch (error) {
|
|
1354
|
+
waiter.reject(error instanceof Error ? error : new RelayPublishError("count failed", this.url));
|
|
1295
1355
|
}
|
|
1296
1356
|
});
|
|
1297
1357
|
}
|
|
1298
|
-
/**
|
|
1299
|
-
* NIP-42 AUTH: sign the current challenge and wait for OK.
|
|
1300
|
-
*/
|
|
1358
|
+
/** NIP-42 AUTH: sign the current challenge and wait for OK. */
|
|
1301
1359
|
async auth(sign, opts) {
|
|
1302
1360
|
const challenge = this.#challenge;
|
|
1303
|
-
if (
|
|
1361
|
+
if (challenge === void 0 || challenge === "") throw new RelayError("no AUTH challenge received from relay", this.url);
|
|
1304
1362
|
if (this.#authPromise) return this.#authPromise;
|
|
1363
|
+
if (this.#answeredChallenge === challenge && this.#answeredResult !== void 0) return this.#answeredResult;
|
|
1305
1364
|
const pending = (async () => {
|
|
1306
|
-
const
|
|
1365
|
+
const template = makeAuthEvent(this.url, challenge);
|
|
1366
|
+
let event;
|
|
1367
|
+
try {
|
|
1368
|
+
event = await sign(template);
|
|
1369
|
+
} catch (error) {
|
|
1370
|
+
if (error instanceof NoSignerError) return {
|
|
1371
|
+
ok: false,
|
|
1372
|
+
message: "auth: no signer"
|
|
1373
|
+
};
|
|
1374
|
+
throw error;
|
|
1375
|
+
}
|
|
1307
1376
|
if (!this.#connected) throw new RelayClosedError("not connected", this.url);
|
|
1308
1377
|
const timeoutMs = opts?.timeoutMs ?? this.#publishTimeoutMs;
|
|
1309
|
-
return
|
|
1378
|
+
return new Promise((resolve, reject) => {
|
|
1310
1379
|
const timer = setTimeout(() => {
|
|
1311
1380
|
this.#publishes.delete(event.id);
|
|
1312
|
-
reject(new
|
|
1381
|
+
reject(new RelayTimeoutError("auth timed out", this.url));
|
|
1313
1382
|
}, timeoutMs);
|
|
1314
1383
|
this.#publishes.set(event.id, {
|
|
1315
|
-
resolve
|
|
1384
|
+
resolve: (result) => {
|
|
1385
|
+
if (this.#challenge === challenge) {
|
|
1386
|
+
this.#answeredChallenge = challenge;
|
|
1387
|
+
this.#answeredResult = result;
|
|
1388
|
+
}
|
|
1389
|
+
resolve(result);
|
|
1390
|
+
},
|
|
1316
1391
|
reject,
|
|
1317
1392
|
timer,
|
|
1318
1393
|
timeoutMs
|
|
1319
1394
|
});
|
|
1320
1395
|
try {
|
|
1321
1396
|
this.#send(["AUTH", event]);
|
|
1322
|
-
} catch (
|
|
1397
|
+
} catch (error) {
|
|
1323
1398
|
clearTimeout(timer);
|
|
1324
1399
|
this.#publishes.delete(event.id);
|
|
1325
|
-
reject(
|
|
1400
|
+
reject(error instanceof Error ? error : new RelayPublishError("auth send failed", this.url));
|
|
1326
1401
|
}
|
|
1327
1402
|
});
|
|
1328
1403
|
})();
|
|
@@ -1335,17 +1410,32 @@ var Relay = class Relay {
|
|
|
1335
1410
|
if (this.#authPromise === pending) this.#authPromise = void 0;
|
|
1336
1411
|
}
|
|
1337
1412
|
}
|
|
1413
|
+
/**
|
|
1414
|
+
* Clear a cached AUTH rejection for the current challenge so a later `auth()` signs again. A
|
|
1415
|
+
* successful auth and in-flight answers are kept. When a challenge is pending and unanswered,
|
|
1416
|
+
* `onauth` is re-fired so the pool's automatic auth can run once more.
|
|
1417
|
+
*/
|
|
1418
|
+
resetAuth() {
|
|
1419
|
+
if (this.#answeredResult !== void 0 && !this.#answeredResult.ok) {
|
|
1420
|
+
this.#answeredChallenge = void 0;
|
|
1421
|
+
this.#answeredResult = void 0;
|
|
1422
|
+
}
|
|
1423
|
+
if (this.#challenge !== void 0 && this.#challenge !== this.#authedChallenge) {
|
|
1424
|
+
const challenge = this.#challenge;
|
|
1425
|
+
invokeSafely(() => this.onauth?.(challenge));
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1338
1428
|
async #ensureAuthed() {
|
|
1339
1429
|
const signer = this.#authSigner;
|
|
1340
1430
|
if (!signer) return false;
|
|
1341
1431
|
for (let i = 0; i < 3; i++) {
|
|
1342
|
-
if (
|
|
1432
|
+
if (this.#challenge === void 0 || this.#challenge === "") return false;
|
|
1343
1433
|
if (this.#authedChallenge === this.#challenge) return true;
|
|
1344
1434
|
const signed = this.#challenge;
|
|
1345
1435
|
const result = await this.auth(signer);
|
|
1346
1436
|
if (this.#authedChallenge === this.#challenge) return true;
|
|
1347
1437
|
if (!result.ok) {
|
|
1348
|
-
if (
|
|
1438
|
+
if (this.#challenge === void 0 || this.#challenge === "" || this.#challenge === signed) return false;
|
|
1349
1439
|
continue;
|
|
1350
1440
|
}
|
|
1351
1441
|
}
|
|
@@ -1393,16 +1483,16 @@ var Relay = class Relay {
|
|
|
1393
1483
|
return;
|
|
1394
1484
|
}
|
|
1395
1485
|
waiter.timer = setTimeout(() => {
|
|
1396
|
-
waiter.reject(new
|
|
1486
|
+
waiter.reject(new RelayTimeoutError("count timed out", this.url));
|
|
1397
1487
|
}, waiter.timeoutMs);
|
|
1398
1488
|
this.#send([
|
|
1399
1489
|
"COUNT",
|
|
1400
1490
|
id,
|
|
1401
1491
|
...waiter.filters
|
|
1402
1492
|
]);
|
|
1403
|
-
} catch (
|
|
1493
|
+
} catch (error) {
|
|
1404
1494
|
if (!this.#counts.has(id)) return;
|
|
1405
|
-
waiter.reject(
|
|
1495
|
+
waiter.reject(error instanceof Error ? error : new RelayClosedError(reason || "COUNT closed", this.url));
|
|
1406
1496
|
}
|
|
1407
1497
|
}
|
|
1408
1498
|
async #authThenRepublish(waiter, eventId, message) {
|
|
@@ -1441,7 +1531,7 @@ var Relay = class Relay {
|
|
|
1441
1531
|
}
|
|
1442
1532
|
waiter.timer = setTimeout(() => {
|
|
1443
1533
|
this.#publishes.delete(eventId);
|
|
1444
|
-
waiter.reject(new
|
|
1534
|
+
waiter.reject(new RelayTimeoutError("publish timed out", this.url));
|
|
1445
1535
|
}, waiter.timeoutMs);
|
|
1446
1536
|
this.#send(["EVENT", waiter.event]);
|
|
1447
1537
|
} catch {
|
|
@@ -1453,19 +1543,17 @@ var Relay = class Relay {
|
|
|
1453
1543
|
}
|
|
1454
1544
|
}
|
|
1455
1545
|
/**
|
|
1456
|
-
* NIP-77: run Negentropy reconciliation against this relay.
|
|
1457
|
-
*
|
|
1458
|
-
* Does not upload or download events.
|
|
1546
|
+
* NIP-77: run Negentropy reconciliation against this relay. Returns the local-only (`have`) and
|
|
1547
|
+
* remote-only (`need`) event ids. Does not upload or download events.
|
|
1459
1548
|
*
|
|
1460
|
-
* `timeoutMs` is a single wall-clock deadline for the whole session
|
|
1461
|
-
*
|
|
1462
|
-
* Default: {@link RelayOptions.publishTimeoutMs}.
|
|
1549
|
+
* `timeoutMs` is a single wall-clock deadline for the whole session (`NEG-OPEN` through the last
|
|
1550
|
+
* `NEG-MSG`), not a per-message budget. Default: {@link RelayOptions.publishTimeoutMs}.
|
|
1463
1551
|
*/
|
|
1464
1552
|
async negReconcile(filter, storage, opts) {
|
|
1465
1553
|
if (!this.#connected) throw new RelayClosedError("not connected", this.url);
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
const id = opts?.id
|
|
1554
|
+
throwIfAborted(opts?.signal);
|
|
1555
|
+
const canonical = canonicalizeFilter(filter);
|
|
1556
|
+
const id = opts?.id === void 0 ? this.nextSubId("neg") : assertSubscriptionId(opts.id);
|
|
1469
1557
|
const timeoutMs = opts?.timeoutMs ?? this.#publishTimeoutMs;
|
|
1470
1558
|
const prev = this.#neg.get(id);
|
|
1471
1559
|
if (prev) failNegSession(prev, new Nip77Error("closed: replaced by new NEG-OPEN"));
|
|
@@ -1475,7 +1563,7 @@ var Relay = class Relay {
|
|
|
1475
1563
|
return await runWiredNegSession({
|
|
1476
1564
|
session,
|
|
1477
1565
|
storage,
|
|
1478
|
-
filter,
|
|
1566
|
+
filter: canonical,
|
|
1479
1567
|
id,
|
|
1480
1568
|
timeoutMs,
|
|
1481
1569
|
signal: opts?.signal,
|
|
@@ -1500,9 +1588,8 @@ var Relay = class Relay {
|
|
|
1500
1588
|
//#endregion
|
|
1501
1589
|
//#region src/relay/fan-in.ts
|
|
1502
1590
|
/**
|
|
1503
|
-
* Aggregate EOSE/close across routed jobs. One `seen` set.
|
|
1504
|
-
* `
|
|
1505
|
-
* `pendingEose` counts unique `jobIndex:url` keys.
|
|
1591
|
+
* Aggregate EOSE/close across routed jobs. One `seen` set. `pending` counts URL list entries
|
|
1592
|
+
* (duplicates included). `pendingEose` counts unique `jobIndex:url` keys.
|
|
1506
1593
|
*/
|
|
1507
1594
|
function fanIn(pool, jobs, opts = {}) {
|
|
1508
1595
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1521,7 +1608,7 @@ function fanIn(pool, jobs, opts = {}) {
|
|
|
1521
1608
|
clearTimeout(eoseTimer);
|
|
1522
1609
|
eoseTimer = void 0;
|
|
1523
1610
|
}
|
|
1524
|
-
opts.oneose?.();
|
|
1611
|
+
invokeSafely(() => opts.oneose?.());
|
|
1525
1612
|
};
|
|
1526
1613
|
const markEose = (jobIndex, url) => {
|
|
1527
1614
|
const key = `${jobIndex}:${url}`;
|
|
@@ -1541,24 +1628,39 @@ function fanIn(pool, jobs, opts = {}) {
|
|
|
1541
1628
|
if (closed) return;
|
|
1542
1629
|
settleClose();
|
|
1543
1630
|
for (const c of closers) c.close(reason);
|
|
1544
|
-
opts.onclose?.(reason ?? "closed by client");
|
|
1631
|
+
invokeSafely(() => opts.onclose?.(reason ?? "closed by client"));
|
|
1545
1632
|
};
|
|
1546
|
-
if (opts.signal?.aborted) {
|
|
1633
|
+
if (opts.signal?.aborted === true) {
|
|
1547
1634
|
closed = true;
|
|
1548
|
-
opts.onclose?.("aborted");
|
|
1635
|
+
invokeSafely(() => opts.onclose?.("aborted"));
|
|
1549
1636
|
return { close: closeAll };
|
|
1550
1637
|
}
|
|
1551
1638
|
opts.signal?.addEventListener("abort", () => closeAll("aborted"), { once: true });
|
|
1639
|
+
const failUrl = (jobIndex, key) => {
|
|
1640
|
+
markEose(jobIndex, key);
|
|
1641
|
+
pending -= 1;
|
|
1642
|
+
if (pending <= 0 && closers.length === 0 && !closed) {
|
|
1643
|
+
settleClose();
|
|
1644
|
+
invokeSafely(() => opts.onclose?.("all relays failed"));
|
|
1645
|
+
}
|
|
1646
|
+
};
|
|
1552
1647
|
const attach = (relay, job, jobIndex) => {
|
|
1553
1648
|
if (closed) return;
|
|
1649
|
+
const received = opts.receivedEvent;
|
|
1554
1650
|
const sub = relay.subscribe([...job.filters], {
|
|
1555
1651
|
id: jobs.length === 1 ? job.id : void 0,
|
|
1556
1652
|
closeOnEose: opts.closeOnEose,
|
|
1557
|
-
alreadyHaveEvent: (id) =>
|
|
1558
|
-
|
|
1653
|
+
alreadyHaveEvent: (id) => {
|
|
1654
|
+
let have = seen.has(id);
|
|
1655
|
+
invokeSafely(() => {
|
|
1656
|
+
have = have || Boolean(opts.alreadyHaveEvent?.(id));
|
|
1657
|
+
});
|
|
1658
|
+
return have;
|
|
1659
|
+
},
|
|
1660
|
+
receivedEvent: received === void 0 ? void 0 : (id) => invokeSafely(() => received(id, relay.url)),
|
|
1559
1661
|
onevent: (event) => {
|
|
1560
1662
|
seen.add(event.id);
|
|
1561
|
-
opts.onevent?.(event);
|
|
1663
|
+
invokeSafely(() => opts.onevent?.(event, relay.url));
|
|
1562
1664
|
},
|
|
1563
1665
|
oneose: () => markEose(jobIndex, relay.url),
|
|
1564
1666
|
onclose: (reason) => {
|
|
@@ -1566,14 +1668,14 @@ function fanIn(pool, jobs, opts = {}) {
|
|
|
1566
1668
|
pending -= 1;
|
|
1567
1669
|
if (pending <= 0 && !closed) {
|
|
1568
1670
|
settleClose();
|
|
1569
|
-
opts.onclose?.(reason);
|
|
1671
|
+
invokeSafely(() => opts.onclose?.(reason));
|
|
1570
1672
|
}
|
|
1571
1673
|
}
|
|
1572
1674
|
});
|
|
1573
1675
|
closers.push(sub);
|
|
1574
1676
|
};
|
|
1575
|
-
for (
|
|
1576
|
-
const
|
|
1677
|
+
for (const [jobIndex, job] of jobs.entries()) {
|
|
1678
|
+
const jobUrls = /* @__PURE__ */ new Set();
|
|
1577
1679
|
for (const url of job.urls) {
|
|
1578
1680
|
let key;
|
|
1579
1681
|
try {
|
|
@@ -1581,42 +1683,43 @@ function fanIn(pool, jobs, opts = {}) {
|
|
|
1581
1683
|
} catch {
|
|
1582
1684
|
key = url;
|
|
1583
1685
|
}
|
|
1686
|
+
if (jobUrls.has(key)) continue;
|
|
1687
|
+
jobUrls.add(key);
|
|
1584
1688
|
pending += 1;
|
|
1585
1689
|
const eoseKey = `${jobIndex}:${key}`;
|
|
1586
1690
|
if (!eoseAttempted.has(eoseKey)) {
|
|
1587
1691
|
eoseAttempted.add(eoseKey);
|
|
1588
1692
|
pendingEose += 1;
|
|
1589
1693
|
}
|
|
1590
|
-
const failUrl = () => {
|
|
1591
|
-
markEose(jobIndex, key);
|
|
1592
|
-
pending -= 1;
|
|
1593
|
-
if (pending <= 0 && closers.length === 0 && !closed) {
|
|
1594
|
-
settleClose();
|
|
1595
|
-
opts.onclose?.("all relays failed");
|
|
1596
|
-
}
|
|
1597
|
-
};
|
|
1598
1694
|
const tryAttach = (relay) => {
|
|
1599
1695
|
try {
|
|
1600
1696
|
attach(relay, job, jobIndex);
|
|
1601
|
-
} catch (
|
|
1602
|
-
if (
|
|
1603
|
-
failUrl();
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
if (error instanceof RelayClosedError) {
|
|
1699
|
+
failUrl(jobIndex, key);
|
|
1604
1700
|
return;
|
|
1605
1701
|
}
|
|
1606
|
-
throw
|
|
1702
|
+
throw error;
|
|
1607
1703
|
}
|
|
1608
1704
|
};
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1705
|
+
(async () => {
|
|
1706
|
+
let relay;
|
|
1707
|
+
try {
|
|
1708
|
+
relay = await pool.ensureRelay(key, {
|
|
1709
|
+
signal: opts.signal,
|
|
1710
|
+
timeoutMs: opts.connectTimeoutMs
|
|
1711
|
+
});
|
|
1712
|
+
} catch {
|
|
1713
|
+
const existing = pool.getRelay(key);
|
|
1714
|
+
if (existing !== void 0) {
|
|
1715
|
+
tryAttach(existing);
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
failUrl(jobIndex, key);
|
|
1616
1719
|
return;
|
|
1617
1720
|
}
|
|
1618
|
-
|
|
1619
|
-
});
|
|
1721
|
+
tryAttach(relay);
|
|
1722
|
+
})();
|
|
1620
1723
|
}
|
|
1621
1724
|
}
|
|
1622
1725
|
if (opts.eoseTimeoutMs !== void 0 && pending > 0) eoseTimer = setTimeout(() => {
|
|
@@ -1626,31 +1729,53 @@ function fanIn(pool, jobs, opts = {}) {
|
|
|
1626
1729
|
if (pending === 0) queueMicrotask(() => {
|
|
1627
1730
|
if (closed) return;
|
|
1628
1731
|
settleClose();
|
|
1629
|
-
opts.onclose?.("no relays");
|
|
1732
|
+
invokeSafely(() => opts.onclose?.("no relays"));
|
|
1630
1733
|
});
|
|
1631
1734
|
return { close: closeAll };
|
|
1632
1735
|
}
|
|
1633
1736
|
async function fetchRouted(pool, jobs, opts = {}) {
|
|
1737
|
+
throwIfAborted(opts.signal);
|
|
1634
1738
|
const byId = /* @__PURE__ */ new Map();
|
|
1635
|
-
await Promise.all(jobs.flatMap((job) =>
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
}
|
|
1739
|
+
await Promise.all(jobs.flatMap((job) => {
|
|
1740
|
+
const urls = [];
|
|
1741
|
+
for (const raw of job.urls) {
|
|
1742
|
+
let key;
|
|
1743
|
+
try {
|
|
1744
|
+
key = normalizeURL(raw);
|
|
1745
|
+
} catch {
|
|
1746
|
+
key = raw;
|
|
1747
|
+
}
|
|
1748
|
+
if (!urls.includes(key)) urls.push(key);
|
|
1749
|
+
}
|
|
1750
|
+
return urls.map(async (url) => {
|
|
1751
|
+
let batch;
|
|
1752
|
+
let relayUrl;
|
|
1753
|
+
try {
|
|
1754
|
+
const relay = await pool.ensureRelay(url, {
|
|
1755
|
+
signal: opts.signal,
|
|
1756
|
+
timeoutMs: opts.connectTimeoutMs
|
|
1757
|
+
});
|
|
1758
|
+
relayUrl = relay.url;
|
|
1759
|
+
batch = await relay.fetch([...job.filters], {
|
|
1760
|
+
timeoutMs: opts.timeoutMs,
|
|
1761
|
+
signal: opts.signal
|
|
1762
|
+
});
|
|
1763
|
+
} catch {
|
|
1764
|
+
if (opts.signal?.aborted === true) throw abortReason(opts.signal);
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1644
1767
|
for (const event of batch) if (!byId.has(event.id)) byId.set(event.id, event);
|
|
1645
|
-
|
|
1646
|
-
|
|
1768
|
+
for (const event of batch) invokeSafely(() => opts.onevent?.(event, relayUrl));
|
|
1769
|
+
});
|
|
1770
|
+
}));
|
|
1647
1771
|
return [...byId.values()];
|
|
1648
1772
|
}
|
|
1649
1773
|
//#endregion
|
|
1650
1774
|
//#region src/relay/pool.ts
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1775
|
+
function isIdle(relay) {
|
|
1776
|
+
return relay.status !== RelayStatus.Connecting && relay.subscriptionCount === 0 && relay.inFlightCount === 0;
|
|
1777
|
+
}
|
|
1778
|
+
/** Multi-relay coordinator: connection reuse, cross-relay event dedup, fan-out publish. */
|
|
1654
1779
|
var Pool = class {
|
|
1655
1780
|
#relays = /* @__PURE__ */ new Map();
|
|
1656
1781
|
#opts;
|
|
@@ -1659,10 +1784,17 @@ var Pool = class {
|
|
|
1659
1784
|
#idleTimer;
|
|
1660
1785
|
#allowInsecure;
|
|
1661
1786
|
#trustedInsecure;
|
|
1787
|
+
#pinned;
|
|
1788
|
+
/** Per-relay timestamps of verification failures inside the sliding window. */
|
|
1789
|
+
#invalidEvents = /* @__PURE__ */ new Map();
|
|
1790
|
+
/** Normalized URL → epoch ms when its suspension lifts. */
|
|
1791
|
+
#suspendedUntil = /* @__PURE__ */ new Map();
|
|
1792
|
+
#resumeTimers = /* @__PURE__ */ new Map();
|
|
1662
1793
|
constructor(opts = {}) {
|
|
1663
1794
|
this.#opts = opts;
|
|
1664
|
-
this.#allowInsecure = opts.allowInsecure ??
|
|
1795
|
+
this.#allowInsecure = opts.allowInsecure ?? false;
|
|
1665
1796
|
this.#trustedInsecure = new Set((opts.trustedInsecureUrls ?? []).map(normalizeURL));
|
|
1797
|
+
this.#pinned = new Set((opts.pinnedUrls ?? []).map(normalizeURL));
|
|
1666
1798
|
this.#idleTimeoutMs = opts.idleTimeoutMs ?? 0;
|
|
1667
1799
|
if (this.#idleTimeoutMs > 0) this.#idleTimer = setInterval(() => this.cleanIdleRelays(), opts.idleCleanupIntervalMs ?? 3e4);
|
|
1668
1800
|
}
|
|
@@ -1672,23 +1804,66 @@ var Pool = class {
|
|
|
1672
1804
|
setTrustedInsecureUrls(urls) {
|
|
1673
1805
|
this.#trustedInsecure = new Set(urls.map(normalizeURL));
|
|
1674
1806
|
}
|
|
1807
|
+
setPinnedUrls(urls) {
|
|
1808
|
+
this.#pinned = new Set(urls.map(normalizeURL));
|
|
1809
|
+
}
|
|
1810
|
+
/**
|
|
1811
|
+
* Drop cached AUTH rejections and re-fire pending challenges on every pooled relay, so a later
|
|
1812
|
+
* `setSigner` takes effect without reconnect.
|
|
1813
|
+
*/
|
|
1814
|
+
resetAuth() {
|
|
1815
|
+
for (const relay of this.#relays.values()) relay.resetAuth();
|
|
1816
|
+
}
|
|
1675
1817
|
cleanIdleRelays() {
|
|
1676
1818
|
if (this.#idleTimeoutMs <= 0) return;
|
|
1677
1819
|
const now = Date.now();
|
|
1678
1820
|
const idle = [];
|
|
1679
1821
|
for (const [url, relay] of this.#relays) {
|
|
1680
|
-
if (
|
|
1822
|
+
if (this.#pinned.has(url) || !isIdle(relay)) continue;
|
|
1681
1823
|
const last = this.#lastActivity.get(url) ?? 0;
|
|
1682
1824
|
if (!relay.connected || now - last >= this.#idleTimeoutMs) idle.push(url);
|
|
1683
1825
|
}
|
|
1684
1826
|
for (const url of this.#lastActivity.keys()) if (!this.#relays.has(url)) this.#lastActivity.delete(url);
|
|
1685
1827
|
if (idle.length === 0) return;
|
|
1686
1828
|
this.close(idle);
|
|
1687
|
-
this.#opts.onIdleRelaysClosed?.(idle);
|
|
1829
|
+
invokeSafely(() => this.#opts.onIdleRelaysClosed?.(idle));
|
|
1688
1830
|
}
|
|
1689
1831
|
#touch(url) {
|
|
1690
1832
|
this.#lastActivity.set(url, Date.now());
|
|
1691
1833
|
}
|
|
1834
|
+
/**
|
|
1835
|
+
* Count one verification failure for `norm`; on the `limit + 1`-th inside `windowMs` suspend the
|
|
1836
|
+
* relay: drop its connection (subscriptions kept), record `until`, and fire `onRelaySuspended`.
|
|
1837
|
+
*/
|
|
1838
|
+
#noteInvalidEvent(norm) {
|
|
1839
|
+
const policy = this.#opts.invalidEventPolicy;
|
|
1840
|
+
if (policy === void 0) return;
|
|
1841
|
+
const now = Date.now();
|
|
1842
|
+
const window = (this.#invalidEvents.get(norm) ?? []).filter((at) => now - at < policy.windowMs);
|
|
1843
|
+
window.push(now);
|
|
1844
|
+
if (window.length <= policy.limit) {
|
|
1845
|
+
this.#invalidEvents.set(norm, window);
|
|
1846
|
+
return;
|
|
1847
|
+
}
|
|
1848
|
+
this.#invalidEvents.delete(norm);
|
|
1849
|
+
if (this.#suspendedUntil.has(norm)) return;
|
|
1850
|
+
const until = now + policy.cooldownMs;
|
|
1851
|
+
this.#suspendedUntil.set(norm, until);
|
|
1852
|
+
this.#relays.get(norm)?.disconnect();
|
|
1853
|
+
const resume = setTimeout(() => {
|
|
1854
|
+
this.#resumeTimers.delete(norm);
|
|
1855
|
+
this.#suspendedUntil.delete(norm);
|
|
1856
|
+
const relay = this.#relays.get(norm);
|
|
1857
|
+
if (relay === void 0 || relay.connected || relay.subscriptionCount === 0) return;
|
|
1858
|
+
(async () => {
|
|
1859
|
+
try {
|
|
1860
|
+
await relay.connect();
|
|
1861
|
+
} catch {}
|
|
1862
|
+
})();
|
|
1863
|
+
}, policy.cooldownMs);
|
|
1864
|
+
this.#resumeTimers.set(norm, resume);
|
|
1865
|
+
invokeSafely(() => this.#opts.onRelaySuspended?.(norm, until));
|
|
1866
|
+
}
|
|
1692
1867
|
#rejectInsecure(url, norm) {
|
|
1693
1868
|
if (this.#allowInsecure) return;
|
|
1694
1869
|
if (!isInsecureRelayUrl(url)) return;
|
|
@@ -1700,11 +1875,40 @@ var Pool = class {
|
|
|
1700
1875
|
clearInterval(this.#idleTimer);
|
|
1701
1876
|
this.#idleTimer = void 0;
|
|
1702
1877
|
}
|
|
1878
|
+
/**
|
|
1879
|
+
* Soft cap: closing the least-recently-used idle non-pinned relay when a new non-pinned relay
|
|
1880
|
+
* would push the count past `maxRelays`. Busy relays are never evicted; with none idle the cap is
|
|
1881
|
+
* exceeded rather than failing.
|
|
1882
|
+
*/
|
|
1883
|
+
#enforceMaxRelays(incoming) {
|
|
1884
|
+
const cap = this.#opts.maxRelays;
|
|
1885
|
+
if (cap === void 0 || this.#pinned.has(incoming)) return;
|
|
1886
|
+
let count = 0;
|
|
1887
|
+
let oldestUrl;
|
|
1888
|
+
let oldestAt = Number.POSITIVE_INFINITY;
|
|
1889
|
+
for (const [url, relay] of this.#relays) {
|
|
1890
|
+
if (this.#pinned.has(url)) continue;
|
|
1891
|
+
count += 1;
|
|
1892
|
+
if (!isIdle(relay)) continue;
|
|
1893
|
+
const at = this.#lastActivity.get(url) ?? 0;
|
|
1894
|
+
if (at < oldestAt) {
|
|
1895
|
+
oldestAt = at;
|
|
1896
|
+
oldestUrl = url;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
if (count >= cap && oldestUrl !== void 0) this.close([oldestUrl]);
|
|
1900
|
+
}
|
|
1703
1901
|
async ensureRelay(url, opts) {
|
|
1704
1902
|
const norm = normalizeURL(url);
|
|
1705
1903
|
this.#rejectInsecure(url, norm);
|
|
1904
|
+
const suspendedUntil = this.#suspendedUntil.get(norm);
|
|
1905
|
+
if (suspendedUntil !== void 0) {
|
|
1906
|
+
if (Date.now() < suspendedUntil) throw new RelaySuspendedError(norm, suspendedUntil);
|
|
1907
|
+
this.#suspendedUntil.delete(norm);
|
|
1908
|
+
}
|
|
1706
1909
|
let relay = this.#relays.get(norm);
|
|
1707
1910
|
if (!relay) {
|
|
1911
|
+
this.#enforceMaxRelays(norm);
|
|
1708
1912
|
const signFn = this.#opts.automaticallyAuth?.(norm) ?? void 0;
|
|
1709
1913
|
const created = new Relay(norm, {
|
|
1710
1914
|
websocketImplementation: this.#opts.websocketImplementation,
|
|
@@ -1722,8 +1926,15 @@ var Pool = class {
|
|
|
1722
1926
|
this.#relays.delete(norm);
|
|
1723
1927
|
this.#lastActivity.delete(norm);
|
|
1724
1928
|
};
|
|
1929
|
+
created.oninvalidevent = () => {
|
|
1930
|
+
this.#noteInvalidEvent(norm);
|
|
1931
|
+
};
|
|
1725
1932
|
if (signFn) created.onauth = () => {
|
|
1726
|
-
|
|
1933
|
+
(async () => {
|
|
1934
|
+
try {
|
|
1935
|
+
await created.auth(signFn);
|
|
1936
|
+
} catch {}
|
|
1937
|
+
})();
|
|
1727
1938
|
};
|
|
1728
1939
|
this.#relays.set(norm, created);
|
|
1729
1940
|
relay = created;
|
|
@@ -1734,18 +1945,22 @@ var Pool = class {
|
|
|
1734
1945
|
signal: opts?.signal,
|
|
1735
1946
|
timeoutMs: opts?.timeoutMs ?? this.#opts.connectTimeoutMs
|
|
1736
1947
|
});
|
|
1737
|
-
} catch (
|
|
1738
|
-
if (
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
if (this.#opts.enableReconnect !== true) {
|
|
1739
1950
|
this.#relays.delete(norm);
|
|
1740
1951
|
this.#lastActivity.delete(norm);
|
|
1741
1952
|
}
|
|
1742
|
-
throw
|
|
1953
|
+
throw error;
|
|
1743
1954
|
}
|
|
1744
1955
|
return relay;
|
|
1745
1956
|
}
|
|
1746
1957
|
close(urls) {
|
|
1747
1958
|
if (!urls) {
|
|
1748
1959
|
this.#stopIdleCleanup();
|
|
1960
|
+
for (const timer of this.#resumeTimers.values()) clearTimeout(timer);
|
|
1961
|
+
this.#resumeTimers.clear();
|
|
1962
|
+
this.#suspendedUntil.clear();
|
|
1963
|
+
this.#invalidEvents.clear();
|
|
1749
1964
|
for (const relay of this.#relays.values()) relay.close();
|
|
1750
1965
|
this.#relays.clear();
|
|
1751
1966
|
this.#lastActivity.clear();
|
|
@@ -1753,22 +1968,27 @@ var Pool = class {
|
|
|
1753
1968
|
}
|
|
1754
1969
|
for (const url of urls) {
|
|
1755
1970
|
const norm = normalizeURL(url);
|
|
1971
|
+
const timer = this.#resumeTimers.get(norm);
|
|
1972
|
+
if (timer !== void 0) {
|
|
1973
|
+
clearTimeout(timer);
|
|
1974
|
+
this.#resumeTimers.delete(norm);
|
|
1975
|
+
}
|
|
1756
1976
|
this.#relays.get(norm)?.close();
|
|
1757
1977
|
this.#relays.delete(norm);
|
|
1758
1978
|
this.#lastActivity.delete(norm);
|
|
1759
1979
|
}
|
|
1760
1980
|
}
|
|
1761
1981
|
/**
|
|
1762
|
-
* Subscribe across relays. Deduplicates by event id.
|
|
1763
|
-
*
|
|
1982
|
+
* Subscribe across relays. Deduplicates by event id. Returns a closer; callbacks receive every
|
|
1983
|
+
* new event once.
|
|
1764
1984
|
*/
|
|
1765
1985
|
subscribe(relays, filters, opts = {}) {
|
|
1766
1986
|
if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
|
|
1767
1987
|
if (opts.id !== void 0) assertSubscriptionId(opts.id);
|
|
1768
|
-
|
|
1988
|
+
const canonical = canonicalizeFilters(filters);
|
|
1769
1989
|
return fanIn(this, [{
|
|
1770
1990
|
urls: relays,
|
|
1771
|
-
filters,
|
|
1991
|
+
filters: canonical,
|
|
1772
1992
|
id: opts.id
|
|
1773
1993
|
}], {
|
|
1774
1994
|
onevent: opts.onevent,
|
|
@@ -1791,7 +2011,8 @@ var Pool = class {
|
|
|
1791
2011
|
}], {
|
|
1792
2012
|
timeoutMs: opts?.timeoutMs,
|
|
1793
2013
|
signal: opts?.signal,
|
|
1794
|
-
connectTimeoutMs: this.#opts.connectTimeoutMs
|
|
2014
|
+
connectTimeoutMs: this.#opts.connectTimeoutMs,
|
|
2015
|
+
onevent: opts?.onevent
|
|
1795
2016
|
});
|
|
1796
2017
|
}
|
|
1797
2018
|
/** Publish to all listed relays; returns per-relay outcomes. */
|
|
@@ -1805,10 +2026,10 @@ var Pool = class {
|
|
|
1805
2026
|
url: relay.url,
|
|
1806
2027
|
result
|
|
1807
2028
|
};
|
|
1808
|
-
} catch (
|
|
2029
|
+
} catch (error) {
|
|
1809
2030
|
return {
|
|
1810
2031
|
url,
|
|
1811
|
-
error:
|
|
2032
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1812
2033
|
};
|
|
1813
2034
|
}
|
|
1814
2035
|
}));
|
|
@@ -1827,11 +2048,12 @@ var Pool = class {
|
|
|
1827
2048
|
}));
|
|
1828
2049
|
}
|
|
1829
2050
|
/**
|
|
1830
|
-
* NIP-45 COUNT across relays. Per-relay outcomes; failures do not throw.
|
|
1831
|
-
*
|
|
2051
|
+
* NIP-45 COUNT across relays. Per-relay outcomes; failures do not throw. Counts are not summed —
|
|
2052
|
+
* each relay reports independently (may overlap).
|
|
1832
2053
|
*/
|
|
1833
2054
|
async count(relays, filters, opts) {
|
|
1834
|
-
|
|
2055
|
+
throwIfAborted(opts?.signal);
|
|
2056
|
+
const canonical = canonicalizeFilters(filters);
|
|
1835
2057
|
return await Promise.all(relays.map(async (url) => {
|
|
1836
2058
|
try {
|
|
1837
2059
|
const relay = await this.ensureRelay(url, {
|
|
@@ -1839,20 +2061,22 @@ var Pool = class {
|
|
|
1839
2061
|
timeoutMs: this.#opts.connectTimeoutMs
|
|
1840
2062
|
});
|
|
1841
2063
|
this.#touch(relay.url);
|
|
1842
|
-
const payload = await relay.count(
|
|
2064
|
+
const payload = await relay.count(canonical, {
|
|
1843
2065
|
timeoutMs: opts?.timeoutMs,
|
|
1844
2066
|
signal: opts?.signal
|
|
1845
2067
|
});
|
|
1846
|
-
|
|
2068
|
+
const out = {
|
|
1847
2069
|
url: relay.url,
|
|
1848
|
-
count: payload.count
|
|
1849
|
-
approximate: payload.approximate,
|
|
1850
|
-
hll: payload.hll
|
|
2070
|
+
count: payload.count
|
|
1851
2071
|
};
|
|
1852
|
-
|
|
2072
|
+
if (payload.approximate !== void 0) out.approximate = payload.approximate;
|
|
2073
|
+
if (payload.hll !== void 0) out.hll = payload.hll;
|
|
2074
|
+
return out;
|
|
2075
|
+
} catch (error) {
|
|
2076
|
+
if (opts?.signal?.aborted === true) throw abortReason(opts.signal);
|
|
1853
2077
|
return {
|
|
1854
2078
|
url,
|
|
1855
|
-
error:
|
|
2079
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1856
2080
|
};
|
|
1857
2081
|
}
|
|
1858
2082
|
}));
|
|
@@ -1876,6 +2100,6 @@ var Pool = class {
|
|
|
1876
2100
|
}
|
|
1877
2101
|
};
|
|
1878
2102
|
//#endregion
|
|
1879
|
-
export { RelayStatus as a, isInsecureRelayUrl as c, RelayClosedError as d, RelayConnectionError as f, 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 };
|
|
2103
|
+
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 };
|
|
1880
2104
|
|
|
1881
|
-
//# sourceMappingURL=pool-
|
|
2105
|
+
//# sourceMappingURL=pool-C2xOcsun.mjs.map
|