@qntx/nostr 0.6.0 → 0.8.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.
Files changed (183) hide show
  1. package/dist/{abort-Ba8tDrOI.mjs → abort-DBq2STPj.mjs} +13 -6
  2. package/dist/abort-DBq2STPj.mjs.map +1 -0
  3. package/dist/{builder-sSflu6nB.d.mts → builder-CTwkB2zM.d.mts} +14 -15
  4. package/dist/{builder-9tjI6WQ3.mjs → builder-aNU2gGFP.mjs} +11 -17
  5. package/dist/builder-aNU2gGFP.mjs.map +1 -0
  6. package/dist/{client-CadO5fAX.mjs → client-Cj2lUaVh.mjs} +141 -353
  7. package/dist/client-Cj2lUaVh.mjs.map +1 -0
  8. package/dist/client.d.mts +2 -2
  9. package/dist/client.mjs +2 -2
  10. package/dist/collections-DtwMc1NU.mjs +33 -0
  11. package/dist/collections-DtwMc1NU.mjs.map +1 -0
  12. package/dist/core.d.mts +9 -8
  13. package/dist/core.mjs +7 -7
  14. package/dist/emitter-DkCY6NkU.mjs +32 -0
  15. package/dist/emitter-DkCY6NkU.mjs.map +1 -0
  16. package/dist/{error-BW7re1UN.d.mts → error-BD_0QFKT.d.mts} +2 -2
  17. package/dist/{error-DrEIM7FO.mjs → error-BILMe3Gr.mjs} +2 -2
  18. package/dist/{error-DrEIM7FO.mjs.map → error-BILMe3Gr.mjs.map} +1 -1
  19. package/dist/{error-CR8Urc84.d.mts → error-BquXDmIN.d.mts} +4 -4
  20. package/dist/{error-B__OMBRY.mjs → error-DlhGW4mJ.mjs} +2 -2
  21. package/dist/{error-B__OMBRY.mjs.map → error-DlhGW4mJ.mjs.map} +1 -1
  22. package/dist/{event-D8Nfoh4K.mjs → event-CHf47dPM.mjs} +16 -10
  23. package/dist/event-CHf47dPM.mjs.map +1 -0
  24. package/dist/{event-fU3Y01PR.d.mts → event-DJeb_DIL.d.mts} +4 -2
  25. package/dist/{filter-C0IMByE3.d.mts → filter-jI_9htF7.d.mts} +2 -2
  26. package/dist/{gossip-oloDMWEj.mjs → gossip-DFxIBlsr.mjs} +23 -33
  27. package/dist/gossip-DFxIBlsr.mjs.map +1 -0
  28. package/dist/{gossip-DpIvkBuj.d.mts → gossip-OOeeFzG5.d.mts} +37 -28
  29. package/dist/gossip.d.mts +1 -1
  30. package/dist/gossip.mjs +1 -1
  31. package/dist/{index-D1bqFHKB.d.mts → index-BxCBX-8R.d.mts} +18 -18
  32. package/dist/{index-lvXeER5t.d.mts → index-CPVEyzNX.d.mts} +32 -50
  33. package/dist/{index-MxC28S4P.d.mts → index-C_w2WfVz.d.mts} +27 -29
  34. package/dist/{index-CNl2q67H.d.mts → index-Cl_1XdYP.d.mts} +111 -180
  35. package/dist/{index-CvkvqB16.d.mts → index-DU9tRFTZ.d.mts} +7 -20
  36. package/dist/{index-q4MDce_r.d.mts → index-DyKwQnbt.d.mts} +6 -8
  37. package/dist/index.d.mts +23 -21
  38. package/dist/index.mjs +21 -19
  39. package/dist/{instance-DQE8BoWt.mjs → instance-C62w3cEt.mjs} +26 -27
  40. package/dist/instance-C62w3cEt.mjs.map +1 -0
  41. package/dist/{instance-CIca-ynn.d.mts → instance-DjsL8h84.d.mts} +5 -5
  42. package/dist/{key-Cv4_pN3x.d.mts → key-D_5OgGT7.d.mts} +11 -7
  43. package/dist/{key-Bps-LqX7.mjs → key-DnVpchl6.mjs} +13 -9
  44. package/dist/key-DnVpchl6.mjs.map +1 -0
  45. package/dist/{loaders-o6YXB_Ri.mjs → loaders-BmPkQfzI.mjs} +73 -82
  46. package/dist/loaders-BmPkQfzI.mjs.map +1 -0
  47. package/dist/loaders.d.mts +3 -3
  48. package/dist/loaders.mjs +2 -2
  49. package/dist/{memory-C2EVx0_O.mjs → memory-CdJ07kub.mjs} +2 -2
  50. package/dist/memory-CdJ07kub.mjs.map +1 -0
  51. package/dist/{memory-index-Do1mz0WF.mjs → memory-index-Cbc83vOZ.mjs} +30 -247
  52. package/dist/memory-index-Cbc83vOZ.mjs.map +1 -0
  53. package/dist/message-oNr7t0SJ.mjs +194 -0
  54. package/dist/message-oNr7t0SJ.mjs.map +1 -0
  55. package/dist/{message-iGPIqnn0.d.mts → message-tclYCVNc.d.mts} +6 -6
  56. package/dist/{nip17-CKIufV98.d.mts → nip17-B5WgW4C4.d.mts} +6 -7
  57. package/dist/{nip19-BevCJZsp.d.mts → nip19-DpPN17bY.d.mts} +25 -31
  58. package/dist/{nip46-DDV4dC56.d.mts → nip46-BHaxC9TZ.d.mts} +19 -21
  59. package/dist/nip51-CR0Q8eym.d.mts +87 -0
  60. package/dist/{nip59-zQgdLcEr.d.mts → nip59-C-RWFQIv.d.mts} +11 -5
  61. package/dist/nip65-D1fbmhOg.d.mts +23 -0
  62. package/dist/{nip77-DH4RhS5r.d.mts → nip77-DzUwUxHq.d.mts} +8 -9
  63. package/dist/nips/blossom.d.mts +17 -13
  64. package/dist/nips/blossom.mjs +30 -9
  65. package/dist/nips/blossom.mjs.map +1 -1
  66. package/dist/nips/nip04.mjs +17 -7
  67. package/dist/nips/nip04.mjs.map +1 -1
  68. package/dist/nips/nip05.d.mts +9 -10
  69. package/dist/nips/nip05.mjs +1 -4
  70. package/dist/nips/nip05.mjs.map +1 -1
  71. package/dist/nips/nip10.d.mts +8 -8
  72. package/dist/nips/nip10.mjs +34 -32
  73. package/dist/nips/nip10.mjs.map +1 -1
  74. package/dist/nips/nip11.d.mts +28 -28
  75. package/dist/nips/nip11.mjs +4 -4
  76. package/dist/nips/nip11.mjs.map +1 -1
  77. package/dist/nips/nip13.d.mts +7 -7
  78. package/dist/nips/nip13.mjs +32 -10
  79. package/dist/nips/nip13.mjs.map +1 -1
  80. package/dist/nips/nip17.d.mts +1 -1
  81. package/dist/nips/nip17.mjs +16 -34
  82. package/dist/nips/nip17.mjs.map +1 -1
  83. package/dist/nips/nip19.d.mts +2 -2
  84. package/dist/nips/nip19.mjs +16 -28
  85. package/dist/nips/nip19.mjs.map +1 -1
  86. package/dist/nips/nip21.d.mts +9 -4
  87. package/dist/nips/nip21.mjs +15 -5
  88. package/dist/nips/nip21.mjs.map +1 -1
  89. package/dist/nips/nip27.d.mts +22 -22
  90. package/dist/nips/nip27.mjs +2 -2
  91. package/dist/nips/nip27.mjs.map +1 -1
  92. package/dist/nips/nip42.d.mts +1 -1
  93. package/dist/nips/nip42.mjs +2 -1
  94. package/dist/nips/nip42.mjs.map +1 -1
  95. package/dist/nips/nip44.mjs +2 -2
  96. package/dist/nips/nip44.mjs.map +1 -1
  97. package/dist/nips/nip46.d.mts +2 -2
  98. package/dist/nips/nip46.mjs +15 -19
  99. package/dist/nips/nip46.mjs.map +1 -1
  100. package/dist/nips/nip49.d.mts +9 -9
  101. package/dist/nips/nip49.mjs +4 -4
  102. package/dist/nips/nip49.mjs.map +1 -1
  103. package/dist/nips/nip51.d.mts +2 -86
  104. package/dist/nips/nip51.mjs +7 -23
  105. package/dist/nips/nip51.mjs.map +1 -1
  106. package/dist/nips/nip57.d.mts +25 -23
  107. package/dist/nips/nip57.mjs +12 -17
  108. package/dist/nips/nip57.mjs.map +1 -1
  109. package/dist/nips/nip59.d.mts +1 -1
  110. package/dist/nips/nip59.mjs +27 -19
  111. package/dist/nips/nip59.mjs.map +1 -1
  112. package/dist/nips/nip65.d.mts +2 -2
  113. package/dist/nips/nip65.mjs +14 -37
  114. package/dist/nips/nip65.mjs.map +1 -1
  115. package/dist/nips/nip77.d.mts +1 -1
  116. package/dist/nips/nip77.mjs +33 -32
  117. package/dist/nips/nip77.mjs.map +1 -1
  118. package/dist/nips/nip98.d.mts +5 -5
  119. package/dist/nips/nip98.mjs +8 -8
  120. package/dist/nips/nip98.mjs.map +1 -1
  121. package/dist/{pool-DBIYMtwy.d.mts → pool-CqQvUnsq.d.mts} +132 -108
  122. package/dist/pool-vlw74C4t.mjs +606 -0
  123. package/dist/pool-vlw74C4t.mjs.map +1 -0
  124. package/dist/put-w13ci3sC.mjs +187 -0
  125. package/dist/put-w13ci3sC.mjs.map +1 -0
  126. package/dist/{reactive-BfhPp9a3.mjs → reactive-Tz0otDIP.mjs} +125 -88
  127. package/dist/reactive-Tz0otDIP.mjs.map +1 -0
  128. package/dist/{reactive-7aqeip4A.d.mts → reactive-gGqUrzW8.d.mts} +20 -26
  129. package/dist/relay-quCs7MHD.mjs +1811 -0
  130. package/dist/relay-quCs7MHD.mjs.map +1 -0
  131. package/dist/relay.d.mts +4 -4
  132. package/dist/relay.mjs +3 -2
  133. package/dist/serial-Bzpw-xz3.mjs +21 -0
  134. package/dist/serial-Bzpw-xz3.mjs.map +1 -0
  135. package/dist/{signer-CYQutDzD.mjs → signer-poTGe6Er.mjs} +12 -14
  136. package/dist/signer-poTGe6Er.mjs.map +1 -0
  137. package/dist/signer.d.mts +2 -2
  138. package/dist/signer.mjs +2 -2
  139. package/dist/{storage-Dky3wuoH.mjs → storage-B5IXTVuS.mjs} +28 -51
  140. package/dist/storage-B5IXTVuS.mjs.map +1 -0
  141. package/dist/storage.d.mts +3 -3
  142. package/dist/storage.mjs +4 -4
  143. package/dist/store.d.mts +2 -2
  144. package/dist/store.mjs +1 -1
  145. package/dist/testing.d.mts +19 -19
  146. package/dist/testing.mjs +9 -9
  147. package/dist/testing.mjs.map +1 -1
  148. package/dist/{types-2pCUprRU.d.mts → types-BacwnDtU.d.mts} +10 -10
  149. package/dist/{types-CRQEk0Oi.d.mts → types-DNmiscfd.d.mts} +2 -2
  150. package/dist/{util-LfQirgDk.mjs → util-DMF9AIO0.mjs} +33 -7
  151. package/dist/util-DMF9AIO0.mjs.map +1 -0
  152. package/dist/{verifier-t5WoWbtY.mjs → verifier-B51Wkt5H.mjs} +3 -3
  153. package/dist/verifier-B51Wkt5H.mjs.map +1 -0
  154. package/dist/verifier-CgTFpgsC.d.mts +22 -0
  155. package/dist/wasm.browser.d.mts +3 -3
  156. package/dist/wasm.browser.mjs +3 -3
  157. package/dist/wasm.d.mts +3 -3
  158. package/dist/wasm.mjs +3 -3
  159. package/dist/wasm.mjs.map +1 -1
  160. package/package.json +5 -9
  161. package/dist/abort-Ba8tDrOI.mjs.map +0 -1
  162. package/dist/builder-9tjI6WQ3.mjs.map +0 -1
  163. package/dist/client-CadO5fAX.mjs.map +0 -1
  164. package/dist/event-D8Nfoh4K.mjs.map +0 -1
  165. package/dist/gossip-oloDMWEj.mjs.map +0 -1
  166. package/dist/instance-DQE8BoWt.mjs.map +0 -1
  167. package/dist/key-Bps-LqX7.mjs.map +0 -1
  168. package/dist/loaders-o6YXB_Ri.mjs.map +0 -1
  169. package/dist/memory-C2EVx0_O.mjs.map +0 -1
  170. package/dist/memory-index-Do1mz0WF.mjs.map +0 -1
  171. package/dist/message-DcY6Fi9o.mjs +0 -193
  172. package/dist/message-DcY6Fi9o.mjs.map +0 -1
  173. package/dist/nip65-CjgmdpPB.d.mts +0 -26
  174. package/dist/nips/nip96.d.mts +0 -40
  175. package/dist/nips/nip96.mjs +0 -118
  176. package/dist/nips/nip96.mjs.map +0 -1
  177. package/dist/pool-As20OGxB.mjs +0 -2166
  178. package/dist/pool-As20OGxB.mjs.map +0 -1
  179. package/dist/reactive-BfhPp9a3.mjs.map +0 -1
  180. package/dist/signer-CYQutDzD.mjs.map +0 -1
  181. package/dist/storage-Dky3wuoH.mjs.map +0 -1
  182. package/dist/util-LfQirgDk.mjs.map +0 -1
  183. package/dist/verifier-t5WoWbtY.mjs.map +0 -1
@@ -0,0 +1,606 @@
1
+ import { S as toError, _ as MessageError, u as normalizeURL } from "./util-DMF9AIO0.mjs";
2
+ import { n as canonicalizeFilters } from "./filter-BQl3GlHt.mjs";
3
+ import { t as assertSubscriptionId } from "./message-oNr7t0SJ.mjs";
4
+ import { t as invokeSafely } from "./report-CHld4uWj.mjs";
5
+ import { c as RelayConnectionError, d as RelaySuspendedError, n as Relay, r as RelayStatus, s as RelayClosedError, u as RelayPublishError } from "./relay-quCs7MHD.mjs";
6
+ import { i as throwIfAborted, n as onAbort, t as abortReason } from "./abort-DBq2STPj.mjs";
7
+ import { t as Emitter } from "./emitter-DkCY6NkU.mjs";
8
+ //#region src/relay/url.ts
9
+ /**
10
+ * {@link normalizeURL}, or the raw string when it is not a valid relay URL (ensureRelay then fails
11
+ * it like a dead relay).
12
+ */
13
+ function relayUrlKey(url) {
14
+ try {
15
+ return normalizeURL(url);
16
+ } catch {
17
+ return url;
18
+ }
19
+ }
20
+ /** Unique relay keys in first-seen order. */
21
+ function uniqueRelayUrls(urls) {
22
+ const seen = /* @__PURE__ */ new Set();
23
+ const unique = [];
24
+ for (const url of urls) {
25
+ const key = relayUrlKey(url);
26
+ if (seen.has(key)) continue;
27
+ seen.add(key);
28
+ unique.push(key);
29
+ }
30
+ return unique;
31
+ }
32
+ /**
33
+ * True for `ws:` / `http:` URLs that are not `.onion`. Local-network `ws://` is still insecure;
34
+ * allow those via a trusted set.
35
+ */
36
+ function isInsecureRelayUrl(url) {
37
+ try {
38
+ const parsed = new URL(url);
39
+ if (parsed.hostname.endsWith(".onion")) return false;
40
+ return parsed.protocol === "ws:" || parsed.protocol === "http:";
41
+ } catch {
42
+ return false;
43
+ }
44
+ }
45
+ //#endregion
46
+ //#region src/relay/fan-in.ts
47
+ /**
48
+ * Aggregate EOSE/close across routed jobs. One `seen` set. `pending` counts URL list entries
49
+ * (duplicates included). `pendingEose` counts unique `jobIndex:url` keys.
50
+ */
51
+ function fanIn(pool, jobs, opts = {}) {
52
+ const seen = /* @__PURE__ */ new Set();
53
+ const closers = [];
54
+ let closed = false;
55
+ let eoseFired = false;
56
+ let sawEose = false;
57
+ let eoseTimer;
58
+ const eoseDone = /* @__PURE__ */ new Set();
59
+ const eoseAttempted = /* @__PURE__ */ new Set();
60
+ let pendingEose = 0;
61
+ let pending = 0;
62
+ const fireEose = () => {
63
+ if (closed || eoseFired) return;
64
+ eoseFired = true;
65
+ if (eoseTimer !== void 0) {
66
+ clearTimeout(eoseTimer);
67
+ eoseTimer = void 0;
68
+ }
69
+ invokeSafely(() => opts.oneose?.());
70
+ };
71
+ /**
72
+ * Remove one relay from the pending-EOSE set. `eosed` credits a real EOSE; a CLOSED or a connect
73
+ * failure only drops the wait — the aggregate `oneose` fires once every remaining relay ended AND
74
+ * at least one actually EOSE'd.
75
+ */
76
+ const settleEose = (jobIndex, url, eosed) => {
77
+ const key = `${jobIndex}:${url}`;
78
+ if (eoseDone.has(key)) return;
79
+ eoseDone.add(key);
80
+ if (eosed) sawEose = true;
81
+ pendingEose -= 1;
82
+ if (pendingEose === 0 && sawEose) fireEose();
83
+ };
84
+ const settleClose = () => {
85
+ closed = true;
86
+ disposeAbort();
87
+ if (eoseTimer !== void 0) {
88
+ clearTimeout(eoseTimer);
89
+ eoseTimer = void 0;
90
+ }
91
+ };
92
+ const closeAll = (reason) => {
93
+ if (closed) return;
94
+ settleClose();
95
+ for (const c of closers) c.close(reason);
96
+ invokeSafely(() => opts.onclose?.(reason ?? "closed by client"));
97
+ };
98
+ if (opts.signal?.aborted === true) {
99
+ closed = true;
100
+ invokeSafely(() => opts.onclose?.("aborted"));
101
+ return { close: closeAll };
102
+ }
103
+ const disposeAbort = onAbort(opts.signal, () => closeAll("aborted"));
104
+ const failUrl = (jobIndex, key) => {
105
+ settleEose(jobIndex, key, false);
106
+ pending -= 1;
107
+ if (pending <= 0 && closers.length === 0 && !closed) {
108
+ settleClose();
109
+ invokeSafely(() => opts.onclose?.("all relays failed"));
110
+ }
111
+ };
112
+ const attach = (relay, job, jobIndex) => {
113
+ if (closed) return;
114
+ const received = opts.receivedEvent;
115
+ const sub = relay.subscribe([...job.filters], {
116
+ id: jobs.length === 1 ? job.id : void 0,
117
+ closeOnEose: opts.closeOnEose,
118
+ alreadyHaveEvent: (id) => {
119
+ let have = seen.has(id);
120
+ invokeSafely(() => {
121
+ have = have || Boolean(opts.alreadyHaveEvent?.(id));
122
+ });
123
+ return have;
124
+ },
125
+ receivedEvent: received === void 0 ? void 0 : (id) => invokeSafely(() => received(id, relay.url)),
126
+ onevent: (event) => {
127
+ seen.add(event.id);
128
+ invokeSafely(() => opts.onevent?.(event, relay.url));
129
+ },
130
+ oneose: () => settleEose(jobIndex, relay.url, true),
131
+ onclose: (reason) => {
132
+ settleEose(jobIndex, relay.url, false);
133
+ pending -= 1;
134
+ if (pending <= 0 && !closed) {
135
+ settleClose();
136
+ invokeSafely(() => opts.onclose?.(reason));
137
+ }
138
+ }
139
+ });
140
+ closers.push(sub);
141
+ };
142
+ for (const [jobIndex, job] of jobs.entries()) for (const key of uniqueRelayUrls(job.urls)) {
143
+ pending += 1;
144
+ const eoseKey = `${jobIndex}:${key}`;
145
+ if (!eoseAttempted.has(eoseKey)) {
146
+ eoseAttempted.add(eoseKey);
147
+ pendingEose += 1;
148
+ }
149
+ const tryAttach = (relay) => {
150
+ try {
151
+ attach(relay, job, jobIndex);
152
+ } catch (error) {
153
+ if (error instanceof RelayClosedError) {
154
+ failUrl(jobIndex, key);
155
+ return;
156
+ }
157
+ throw error;
158
+ }
159
+ };
160
+ (async () => {
161
+ let relay;
162
+ try {
163
+ relay = await pool.ensureRelay(key, {
164
+ signal: opts.signal,
165
+ timeoutMs: opts.connectTimeoutMs
166
+ });
167
+ } catch {
168
+ const existing = pool.getRelay(key);
169
+ if (existing !== void 0) {
170
+ tryAttach(existing);
171
+ return;
172
+ }
173
+ failUrl(jobIndex, key);
174
+ return;
175
+ }
176
+ tryAttach(relay);
177
+ })();
178
+ }
179
+ if (opts.eoseTimeoutMs !== void 0 && pending > 0) eoseTimer = setTimeout(() => {
180
+ eoseTimer = void 0;
181
+ fireEose();
182
+ }, opts.eoseTimeoutMs);
183
+ if (pending === 0) queueMicrotask(() => {
184
+ if (closed) return;
185
+ settleClose();
186
+ invokeSafely(() => opts.onclose?.("no relays"));
187
+ });
188
+ return { close: closeAll };
189
+ }
190
+ async function fetchRouted(pool, jobs, opts = {}) {
191
+ throwIfAborted(opts.signal);
192
+ const byId = /* @__PURE__ */ new Map();
193
+ await Promise.all(jobs.flatMap((job) => {
194
+ return uniqueRelayUrls(job.urls).map(async (url) => {
195
+ let batch;
196
+ let relayUrl;
197
+ try {
198
+ const relay = await pool.ensureRelay(url, {
199
+ signal: opts.signal,
200
+ timeoutMs: opts.connectTimeoutMs
201
+ });
202
+ relayUrl = relay.url;
203
+ batch = (await relay.fetch([...job.filters], {
204
+ timeoutMs: opts.timeoutMs,
205
+ signal: opts.signal
206
+ })).events;
207
+ } catch {
208
+ if (opts.signal?.aborted === true) throw abortReason(opts.signal);
209
+ return;
210
+ }
211
+ for (const event of batch) if (!byId.has(event.id)) byId.set(event.id, event);
212
+ for (const event of batch) invokeSafely(() => opts.onevent?.(event, relayUrl));
213
+ });
214
+ }));
215
+ return [...byId.values()];
216
+ }
217
+ //#endregion
218
+ //#region src/relay/pool.ts
219
+ const countOk = (url, payload) => ({
220
+ url,
221
+ status: "ok",
222
+ ...payload
223
+ });
224
+ function isIdle(relay) {
225
+ return relay.status !== RelayStatus.Connecting && relay.subscriptionCount === 0 && relay.inFlightCount === 0;
226
+ }
227
+ /** Multi-relay coordinator: connection reuse, cross-relay event dedup, fan-out publish. */
228
+ var Pool = class {
229
+ #relays = /* @__PURE__ */ new Map();
230
+ #relayOptions;
231
+ #automaticallyAuth;
232
+ #maxRelays;
233
+ #invalidEventPolicy;
234
+ #events = new Emitter();
235
+ #lastActivity = /* @__PURE__ */ new Map();
236
+ #idleTimeoutMs;
237
+ #idleTimer;
238
+ #allowInsecure;
239
+ #trustedInsecure;
240
+ #pinned;
241
+ /** Per-relay timestamps of verification failures inside the sliding window. */
242
+ #invalidEvents = /* @__PURE__ */ new Map();
243
+ /** Normalized URL → epoch ms when its suspension lifts. */
244
+ #suspendedUntil = /* @__PURE__ */ new Map();
245
+ #resumeTimers = /* @__PURE__ */ new Map();
246
+ constructor(opts = {}) {
247
+ const { automaticallyAuth, allowInsecure, trustedInsecureUrls, idleTimeoutMs, idleCleanupIntervalMs, maxRelays, pinnedUrls, invalidEventPolicy, ...relayOptions } = opts;
248
+ this.#relayOptions = relayOptions;
249
+ this.#automaticallyAuth = automaticallyAuth;
250
+ this.#maxRelays = maxRelays;
251
+ this.#invalidEventPolicy = invalidEventPolicy;
252
+ this.#allowInsecure = allowInsecure ?? false;
253
+ this.#trustedInsecure = new Set((trustedInsecureUrls ?? []).map(normalizeURL));
254
+ this.#pinned = new Set((pinnedUrls ?? []).map(normalizeURL));
255
+ this.#idleTimeoutMs = idleTimeoutMs ?? 0;
256
+ if (this.#idleTimeoutMs > 0) this.#idleTimer = setInterval(() => this.cleanIdleRelays(), idleCleanupIntervalMs ?? 3e4);
257
+ }
258
+ /**
259
+ * Listen for a pool event. Multiple listeners per type are allowed and fire in registration
260
+ * order; a throwing listener is reported and does not break the dispatch. Returns an unsubscribe
261
+ * function.
262
+ */
263
+ on(type, listener) {
264
+ return this.#events.on(type, listener);
265
+ }
266
+ setAllowInsecure(allow) {
267
+ this.#allowInsecure = allow;
268
+ }
269
+ setTrustedInsecureUrls(urls) {
270
+ this.#trustedInsecure = new Set(urls.map(normalizeURL));
271
+ }
272
+ setPinnedUrls(urls) {
273
+ this.#pinned = new Set(urls.map(normalizeURL));
274
+ }
275
+ /**
276
+ * Drop cached AUTH rejections and re-fire pending challenges on every pooled relay, so a later
277
+ * `setSigner` takes effect without reconnect.
278
+ */
279
+ resetAuth() {
280
+ for (const relay of this.#relays.values()) relay.resetAuth();
281
+ }
282
+ cleanIdleRelays() {
283
+ if (this.#idleTimeoutMs <= 0) return;
284
+ const now = Date.now();
285
+ const idle = [];
286
+ for (const [url, relay] of this.#relays) {
287
+ if (this.#pinned.has(url) || !isIdle(relay)) continue;
288
+ const last = this.#lastActivity.get(url) ?? 0;
289
+ if (!relay.connected || now - last >= this.#idleTimeoutMs) idle.push(url);
290
+ }
291
+ for (const url of this.#lastActivity.keys()) if (!this.#relays.has(url)) this.#lastActivity.delete(url);
292
+ if (idle.length === 0) return;
293
+ this.closeRelays(idle);
294
+ this.#events.emit("idle", idle);
295
+ }
296
+ #touch(url) {
297
+ this.#lastActivity.set(url, Date.now());
298
+ }
299
+ /**
300
+ * Count one verification failure for `norm`; on the `limit + 1`-th inside `windowMs` suspend the
301
+ * relay: drop its connection (subscriptions kept), record `until`, and emit `suspend`.
302
+ */
303
+ #noteInvalidEvent(norm) {
304
+ const policy = this.#invalidEventPolicy;
305
+ if (policy === void 0) return;
306
+ const now = Date.now();
307
+ const window = (this.#invalidEvents.get(norm) ?? []).filter((at) => now - at < policy.windowMs);
308
+ window.push(now);
309
+ if (window.length <= policy.limit) {
310
+ this.#invalidEvents.set(norm, window);
311
+ return;
312
+ }
313
+ this.#invalidEvents.delete(norm);
314
+ if (this.#suspendedUntil.has(norm)) return;
315
+ const until = now + policy.cooldownMs;
316
+ this.#suspendedUntil.set(norm, until);
317
+ this.#relays.get(norm)?.disconnect();
318
+ const resume = setTimeout(() => {
319
+ this.#resumeTimers.delete(norm);
320
+ this.#suspendedUntil.delete(norm);
321
+ const relay = this.#relays.get(norm);
322
+ if (relay === void 0 || relay.connected || relay.subscriptionCount === 0) return;
323
+ (async () => {
324
+ try {
325
+ await relay.connect();
326
+ } catch {}
327
+ })();
328
+ }, policy.cooldownMs);
329
+ this.#resumeTimers.set(norm, resume);
330
+ this.#events.emit("suspend", {
331
+ url: norm,
332
+ until
333
+ });
334
+ }
335
+ #rejectInsecure(url, norm) {
336
+ if (this.#allowInsecure) return;
337
+ if (!isInsecureRelayUrl(url)) return;
338
+ if (this.#trustedInsecure.has(norm)) return;
339
+ throw new RelayConnectionError("insecure relay connection blocked", norm);
340
+ }
341
+ #stopIdleCleanup() {
342
+ if (this.#idleTimer === void 0) return;
343
+ clearInterval(this.#idleTimer);
344
+ this.#idleTimer = void 0;
345
+ }
346
+ /**
347
+ * Soft cap: closing the least-recently-used idle non-pinned relay when a new non-pinned relay
348
+ * would push the count past `maxRelays`. Busy relays are never evicted; with none idle the cap is
349
+ * exceeded rather than failing.
350
+ */
351
+ #enforceMaxRelays(incoming) {
352
+ const cap = this.#maxRelays;
353
+ if (cap === void 0 || this.#pinned.has(incoming)) return;
354
+ let count = 0;
355
+ let oldestUrl;
356
+ let oldestAt = Number.POSITIVE_INFINITY;
357
+ for (const [url, relay] of this.#relays) {
358
+ if (this.#pinned.has(url)) continue;
359
+ count += 1;
360
+ if (!isIdle(relay)) continue;
361
+ const at = this.#lastActivity.get(url) ?? 0;
362
+ if (at < oldestAt) {
363
+ oldestAt = at;
364
+ oldestUrl = url;
365
+ }
366
+ }
367
+ if (count >= cap && oldestUrl !== void 0) this.closeRelays([oldestUrl]);
368
+ }
369
+ async ensureRelay(url, opts) {
370
+ const norm = normalizeURL(url);
371
+ this.#rejectInsecure(url, norm);
372
+ const suspendedUntil = this.#suspendedUntil.get(norm);
373
+ if (suspendedUntil !== void 0) {
374
+ if (Date.now() < suspendedUntil) throw new RelaySuspendedError(norm, suspendedUntil);
375
+ this.#suspendedUntil.delete(norm);
376
+ }
377
+ let relay = this.#relays.get(norm);
378
+ if (!relay) {
379
+ this.#enforceMaxRelays(norm);
380
+ const signFn = this.#automaticallyAuth?.(norm) ?? void 0;
381
+ const created = new Relay(norm, {
382
+ ...this.#relayOptions,
383
+ authSigner: signFn
384
+ });
385
+ created.on("close", () => {
386
+ this.#relays.delete(norm);
387
+ this.#lastActivity.delete(norm);
388
+ });
389
+ created.on("invalidevent", () => {
390
+ this.#noteInvalidEvent(norm);
391
+ });
392
+ if (signFn) created.on("auth", () => {
393
+ (async () => {
394
+ try {
395
+ await created.auth(signFn);
396
+ } catch {}
397
+ })();
398
+ });
399
+ this.#relays.set(norm, created);
400
+ relay = created;
401
+ }
402
+ this.#touch(norm);
403
+ if (!relay.connected) try {
404
+ await relay.connect({
405
+ signal: opts?.signal,
406
+ timeoutMs: opts?.timeoutMs ?? this.#relayOptions.connectTimeoutMs
407
+ });
408
+ } catch (error) {
409
+ if (this.#relayOptions.enableReconnect !== true) {
410
+ this.#relays.delete(norm);
411
+ this.#lastActivity.delete(norm);
412
+ }
413
+ throw error;
414
+ }
415
+ return relay;
416
+ }
417
+ /** Close every pooled relay and clear all pool state. */
418
+ close() {
419
+ this.#stopIdleCleanup();
420
+ for (const timer of this.#resumeTimers.values()) clearTimeout(timer);
421
+ this.#resumeTimers.clear();
422
+ this.#suspendedUntil.clear();
423
+ this.#invalidEvents.clear();
424
+ for (const relay of this.#relays.values()) relay.close();
425
+ this.#relays.clear();
426
+ this.#lastActivity.clear();
427
+ }
428
+ /** Close and forget the listed relays; other pooled relays are untouched. */
429
+ closeRelays(urls) {
430
+ for (const url of urls) {
431
+ const norm = normalizeURL(url);
432
+ const timer = this.#resumeTimers.get(norm);
433
+ if (timer !== void 0) {
434
+ clearTimeout(timer);
435
+ this.#resumeTimers.delete(norm);
436
+ }
437
+ this.#relays.get(norm)?.close();
438
+ this.#relays.delete(norm);
439
+ this.#lastActivity.delete(norm);
440
+ }
441
+ }
442
+ /**
443
+ * Subscribe across relays. Deduplicates by event id. Returns a closer; callbacks receive every
444
+ * new event once.
445
+ */
446
+ subscribe(relays, filters, opts = {}) {
447
+ if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
448
+ if (opts.id !== void 0) assertSubscriptionId(opts.id);
449
+ const canonical = canonicalizeFilters(filters);
450
+ return fanIn(this, [{
451
+ urls: relays,
452
+ filters: canonical,
453
+ id: opts.id
454
+ }], {
455
+ onevent: opts.onevent,
456
+ oneose: opts.oneose,
457
+ onclose: opts.onclose,
458
+ signal: opts.signal,
459
+ eoseTimeoutMs: opts.eoseTimeoutMs,
460
+ alreadyHaveEvent: opts.alreadyHaveEvent,
461
+ receivedEvent: opts.receivedEvent,
462
+ closeOnEose: opts.closeOnEose,
463
+ connectTimeoutMs: this.#relayOptions.connectTimeoutMs
464
+ });
465
+ }
466
+ /**
467
+ * One-shot fetch per relay: each entry reports its own events and end reason (`eose`, `closed`,
468
+ * `timeout`), or `failed` when the connect or REQ itself errored. An abort rejects the whole
469
+ * call. Events are not deduped across relays — use {@link fetch} for that.
470
+ */
471
+ async fetchEach(relays, filters, opts) {
472
+ if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
473
+ throwIfAborted(opts?.signal);
474
+ const canonical = canonicalizeFilters(filters);
475
+ return Promise.all(uniqueRelayUrls(relays).map(async (url) => {
476
+ try {
477
+ const relay = await this.ensureRelay(url, {
478
+ signal: opts?.signal,
479
+ timeoutMs: this.#relayOptions.connectTimeoutMs
480
+ });
481
+ this.#touch(relay.url);
482
+ const result = await relay.fetch([...canonical], {
483
+ timeoutMs: opts?.timeoutMs,
484
+ signal: opts?.signal
485
+ });
486
+ return {
487
+ url,
488
+ events: result.events,
489
+ end: result.end
490
+ };
491
+ } catch (error) {
492
+ if (opts?.signal?.aborted === true) throw abortReason(opts.signal);
493
+ return {
494
+ url,
495
+ events: [],
496
+ end: {
497
+ type: "failed",
498
+ error: toError(error)
499
+ }
500
+ };
501
+ }
502
+ }));
503
+ }
504
+ /** Fetch events until each connected relay EOSE or timeout; dedupe by id. */
505
+ async fetch(relays, filters, opts) {
506
+ if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
507
+ return fetchRouted(this, [{
508
+ urls: relays,
509
+ filters: canonicalizeFilters(filters)
510
+ }], {
511
+ timeoutMs: opts?.timeoutMs,
512
+ signal: opts?.signal,
513
+ connectTimeoutMs: this.#relayOptions.connectTimeoutMs,
514
+ onevent: opts?.onevent
515
+ });
516
+ }
517
+ /** Publish to all listed relays; returns per-relay outcomes. */
518
+ async publish(relays, event, opts) {
519
+ return Promise.all(uniqueRelayUrls(relays).map(async (url) => {
520
+ try {
521
+ const relay = await this.ensureRelay(url, { timeoutMs: this.#relayOptions.connectTimeoutMs });
522
+ this.#touch(relay.url);
523
+ const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });
524
+ return result.ok ? {
525
+ url,
526
+ status: "ok",
527
+ message: result.message
528
+ } : {
529
+ url,
530
+ status: "rejected",
531
+ message: result.message
532
+ };
533
+ } catch (error) {
534
+ return {
535
+ url,
536
+ status: "failed",
537
+ error: toError(error)
538
+ };
539
+ }
540
+ }));
541
+ }
542
+ /** First successful publish (Promise.any semantics). */
543
+ async publishAny(relays, event, opts) {
544
+ return Promise.any(uniqueRelayUrls(relays).map(async (url) => {
545
+ const relay = await this.ensureRelay(url, { timeoutMs: this.#relayOptions.connectTimeoutMs });
546
+ this.#touch(relay.url);
547
+ const result = await relay.publish(event, { timeoutMs: opts?.timeoutMs });
548
+ if (!result.ok) throw new RelayPublishError(result.message || "rejected", relay.url);
549
+ return {
550
+ url,
551
+ status: "ok",
552
+ message: result.message
553
+ };
554
+ }));
555
+ }
556
+ /**
557
+ * NIP-45 COUNT across relays. Per-relay outcomes; failures do not throw. Counts are not summed —
558
+ * each relay reports independently (may overlap).
559
+ */
560
+ async count(relays, filters, opts) {
561
+ throwIfAborted(opts?.signal);
562
+ const canonical = canonicalizeFilters(filters);
563
+ return Promise.all(uniqueRelayUrls(relays).map(async (url) => {
564
+ try {
565
+ const relay = await this.ensureRelay(url, {
566
+ signal: opts?.signal,
567
+ timeoutMs: this.#relayOptions.connectTimeoutMs
568
+ });
569
+ this.#touch(relay.url);
570
+ const payload = await relay.count(canonical, {
571
+ timeoutMs: opts?.timeoutMs,
572
+ signal: opts?.signal
573
+ });
574
+ return countOk(url, payload);
575
+ } catch (error) {
576
+ if (opts?.signal?.aborted === true) throw abortReason(opts.signal);
577
+ return {
578
+ url,
579
+ status: "failed",
580
+ error: toError(error)
581
+ };
582
+ }
583
+ }));
584
+ }
585
+ listRelays() {
586
+ return [...this.#relays.keys()];
587
+ }
588
+ /** Lookup a pooled relay, including disconnected reconnecting entries. */
589
+ getRelay(url) {
590
+ try {
591
+ return this.#relays.get(normalizeURL(url));
592
+ } catch {
593
+ return this.#relays.get(url);
594
+ }
595
+ }
596
+ /** Currently connected URLs. Unlike listRelays(), excludes reconnecting/disconnected entries. */
597
+ connectedUrls() {
598
+ const urls = [];
599
+ for (const [url, relay] of this.#relays) if (relay.connected) urls.push(url);
600
+ return urls;
601
+ }
602
+ };
603
+ //#endregion
604
+ export { isInsecureRelayUrl as i, fanIn as n, fetchRouted as r, Pool as t };
605
+
606
+ //# sourceMappingURL=pool-vlw74C4t.mjs.map