@qntx/nostr 0.7.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.
- package/dist/{builder-DgkqDylK.d.mts → builder-CTwkB2zM.d.mts} +10 -10
- package/dist/{builder-CdLE4J6O.mjs → builder-aNU2gGFP.mjs} +4 -4
- package/dist/builder-aNU2gGFP.mjs.map +1 -0
- package/dist/{client-CILhdfsF.mjs → client-Cj2lUaVh.mjs} +70 -72
- package/dist/client-Cj2lUaVh.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/core.d.mts +2 -2
- package/dist/core.mjs +6 -6
- package/dist/emitter-DkCY6NkU.mjs +32 -0
- package/dist/emitter-DkCY6NkU.mjs.map +1 -0
- package/dist/{error-L_QdmSnV.mjs → error-BILMe3Gr.mjs} +2 -2
- package/dist/{error-L_QdmSnV.mjs.map → error-BILMe3Gr.mjs.map} +1 -1
- package/dist/{error-gtJvV6YS.mjs → error-DlhGW4mJ.mjs} +2 -2
- package/dist/{error-gtJvV6YS.mjs.map → error-DlhGW4mJ.mjs.map} +1 -1
- package/dist/{event-DinQMmJ1.mjs → event-CHf47dPM.mjs} +2 -2
- package/dist/{event-DinQMmJ1.mjs.map → event-CHf47dPM.mjs.map} +1 -1
- package/dist/{gossip-CcTp9VpU.mjs → gossip-DFxIBlsr.mjs} +3 -3
- package/dist/gossip-DFxIBlsr.mjs.map +1 -0
- package/dist/{gossip-C7HLW5kI.d.mts → gossip-OOeeFzG5.d.mts} +4 -4
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/{index-InQZpHyQ.d.mts → index-BxCBX-8R.d.mts} +2 -2
- package/dist/{index-iLT8UzY3.d.mts → index-CPVEyzNX.d.mts} +22 -22
- package/dist/{index-Tx15HGAD.d.mts → index-C_w2WfVz.d.mts} +4 -4
- package/dist/{index-DocgD9K0.d.mts → index-Cl_1XdYP.d.mts} +29 -23
- package/dist/{index-DiXwNn2M.d.mts → index-DyKwQnbt.d.mts} +2 -2
- package/dist/index.d.mts +14 -14
- package/dist/index.mjs +17 -17
- package/dist/{instance-BcKzxHIP.mjs → instance-C62w3cEt.mjs} +3 -3
- package/dist/{instance-BcKzxHIP.mjs.map → instance-C62w3cEt.mjs.map} +1 -1
- package/dist/{key-bshqfmZL.mjs → key-DnVpchl6.mjs} +3 -3
- package/dist/{key-bshqfmZL.mjs.map → key-DnVpchl6.mjs.map} +1 -1
- package/dist/{loaders-DD0XAGzw.mjs → loaders-BmPkQfzI.mjs} +6 -6
- package/dist/loaders-BmPkQfzI.mjs.map +1 -0
- package/dist/loaders.d.mts +2 -2
- package/dist/loaders.mjs +1 -1
- package/dist/{memory-D_-5-T31.mjs → memory-CdJ07kub.mjs} +2 -2
- package/dist/memory-CdJ07kub.mjs.map +1 -0
- package/dist/{memory-index-C6rujtoc.mjs → memory-index-Cbc83vOZ.mjs} +3 -3
- package/dist/{memory-index-C6rujtoc.mjs.map → memory-index-Cbc83vOZ.mjs.map} +1 -1
- package/dist/{message-CWLcFTQ1.mjs → message-oNr7t0SJ.mjs} +3 -3
- package/dist/message-oNr7t0SJ.mjs.map +1 -0
- package/dist/{message-CbBZOhq3.d.mts → message-tclYCVNc.d.mts} +4 -4
- package/dist/{nip17-cTDFfFVt.d.mts → nip17-B5WgW4C4.d.mts} +4 -4
- package/dist/{nip19-D1OnJ192.d.mts → nip19-DpPN17bY.d.mts} +23 -23
- package/dist/{nip46-DrsPI8EX.d.mts → nip46-BHaxC9TZ.d.mts} +17 -17
- package/dist/{nip51-DoPUvQ--.d.mts → nip51-CR0Q8eym.d.mts} +10 -10
- package/dist/{nip59-BFIihLYY.d.mts → nip59-C-RWFQIv.d.mts} +9 -2
- package/dist/{nip65-52HiwiyD.d.mts → nip65-D1fbmhOg.d.mts} +2 -2
- package/dist/{nip77-C9QcZn7b.d.mts → nip77-DzUwUxHq.d.mts} +6 -6
- package/dist/nips/blossom.d.mts +6 -6
- package/dist/nips/blossom.mjs +2 -2
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.mjs +1 -1
- package/dist/nips/nip05.d.mts +8 -8
- package/dist/nips/nip05.mjs +1 -1
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +7 -7
- package/dist/nips/nip10.mjs +34 -32
- package/dist/nips/nip10.mjs.map +1 -1
- package/dist/nips/nip11.d.mts +27 -27
- package/dist/nips/nip11.mjs +1 -1
- package/dist/nips/nip11.mjs.map +1 -1
- package/dist/nips/nip13.mjs +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +6 -4
- package/dist/nips/nip17.mjs.map +1 -1
- package/dist/nips/nip19.d.mts +1 -1
- package/dist/nips/nip19.mjs +1 -1
- package/dist/nips/nip19.mjs.map +1 -1
- package/dist/nips/nip21.d.mts +4 -4
- package/dist/nips/nip21.mjs +1 -1
- package/dist/nips/nip21.mjs.map +1 -1
- package/dist/nips/nip27.d.mts +19 -19
- package/dist/nips/nip27.mjs +2 -2
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.mjs +1 -1
- package/dist/nips/nip44.mjs +2 -2
- package/dist/nips/nip44.mjs.map +1 -1
- package/dist/nips/nip46.d.mts +1 -1
- package/dist/nips/nip46.mjs +1 -1
- package/dist/nips/nip46.mjs.map +1 -1
- package/dist/nips/nip49.d.mts +4 -4
- package/dist/nips/nip49.mjs +1 -1
- package/dist/nips/nip49.mjs.map +1 -1
- package/dist/nips/nip51.d.mts +1 -1
- package/dist/nips/nip51.mjs +3 -3
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.d.mts +18 -18
- package/dist/nips/nip57.mjs +3 -3
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +15 -7
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +2 -2
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +1 -1
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip98.mjs +3 -3
- package/dist/{pool-SzJKPIa3.d.mts → pool-CqQvUnsq.d.mts} +43 -24
- package/dist/{pool-YHRZgcQ3.mjs → pool-vlw74C4t.mjs} +39 -29
- package/dist/pool-vlw74C4t.mjs.map +1 -0
- package/dist/{put-DS0yzVJx.mjs → put-w13ci3sC.mjs} +3 -3
- package/dist/put-w13ci3sC.mjs.map +1 -0
- package/dist/{reactive-BJRoVHxU.mjs → reactive-Tz0otDIP.mjs} +49 -27
- package/dist/reactive-Tz0otDIP.mjs.map +1 -0
- package/dist/{reactive-BJ1ggn4d.d.mts → reactive-gGqUrzW8.d.mts} +16 -7
- package/dist/{relay-CvxPiQ_m.mjs → relay-quCs7MHD.mjs} +1049 -861
- package/dist/relay-quCs7MHD.mjs.map +1 -0
- package/dist/relay.d.mts +4 -4
- package/dist/relay.mjs +2 -2
- package/dist/{signer-yW3slDC2.mjs → signer-poTGe6Er.mjs} +6 -6
- package/dist/signer-poTGe6Er.mjs.map +1 -0
- package/dist/signer.d.mts +1 -1
- package/dist/signer.mjs +2 -2
- package/dist/{storage-CZ52ifcQ.mjs → storage-B5IXTVuS.mjs} +16 -14
- package/dist/storage-B5IXTVuS.mjs.map +1 -0
- package/dist/storage.d.mts +2 -2
- package/dist/storage.mjs +4 -4
- package/dist/store.d.mts +2 -2
- package/dist/store.mjs +1 -1
- package/dist/testing.d.mts +3 -3
- package/dist/testing.mjs +6 -6
- package/dist/testing.mjs.map +1 -1
- package/dist/{types-Cm-XMgtH.d.mts → types-BacwnDtU.d.mts} +5 -5
- package/dist/{util-yFYzlbrm.mjs → util-DMF9AIO0.mjs} +6 -2
- package/dist/util-DMF9AIO0.mjs.map +1 -0
- package/dist/{verifier-BTLOpo1n.mjs → verifier-B51Wkt5H.mjs} +3 -3
- package/dist/{verifier-BTLOpo1n.mjs.map → verifier-B51Wkt5H.mjs.map} +1 -1
- package/dist/wasm.browser.mjs +2 -2
- package/dist/wasm.mjs +2 -2
- package/package.json +1 -1
- package/dist/builder-CdLE4J6O.mjs.map +0 -1
- package/dist/client-CILhdfsF.mjs.map +0 -1
- package/dist/gossip-CcTp9VpU.mjs.map +0 -1
- package/dist/loaders-DD0XAGzw.mjs.map +0 -1
- package/dist/memory-D_-5-T31.mjs.map +0 -1
- package/dist/message-CWLcFTQ1.mjs.map +0 -1
- package/dist/pool-YHRZgcQ3.mjs.map +0 -1
- package/dist/put-DS0yzVJx.mjs.map +0 -1
- package/dist/reactive-BJRoVHxU.mjs.map +0 -1
- package/dist/relay-CvxPiQ_m.mjs.map +0 -1
- package/dist/signer-yW3slDC2.mjs.map +0 -1
- package/dist/storage-CZ52ifcQ.mjs.map +0 -1
- package/dist/util-yFYzlbrm.mjs.map +0 -1
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import { _ as MessageError, b as WasmPoisonedError, u as normalizeURL, v as NostrError } from "./util-
|
|
2
|
-
import { n as verifyEvent } from "./verifier-
|
|
1
|
+
import { _ as MessageError, b as WasmPoisonedError, u as normalizeURL, v as NostrError } from "./util-DMF9AIO0.mjs";
|
|
2
|
+
import { n as verifyEvent } from "./verifier-B51Wkt5H.mjs";
|
|
3
3
|
import { n as canonicalizeFilters, r as filterFingerprint, t as canonicalizeFilter } from "./filter-BQl3GlHt.mjs";
|
|
4
|
-
import { n as createSubscriptionId, r as encodeClientMessage, s as parseRelayMessage, t as assertSubscriptionId } from "./message-
|
|
5
|
-
import { t as NoSignerError } from "./error-
|
|
4
|
+
import { n as createSubscriptionId, r as encodeClientMessage, s as parseRelayMessage, t as assertSubscriptionId } from "./message-oNr7t0SJ.mjs";
|
|
5
|
+
import { t as NoSignerError } from "./error-DlhGW4mJ.mjs";
|
|
6
6
|
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
7
7
|
import { isAuthRequired, makeAuthEvent } from "./nips/nip42.mjs";
|
|
8
8
|
import { i as throwIfAborted, n as onAbort, r as raceSignal, t as abortReason } from "./abort-DBq2STPj.mjs";
|
|
9
|
+
import { t as Emitter } from "./emitter-DkCY6NkU.mjs";
|
|
9
10
|
import { Nip77Error, runNegSession } from "./nips/nip77.mjs";
|
|
10
11
|
//#region src/relay/error.ts
|
|
11
12
|
/** Base class for relay errors; carries the relay URL when known. */
|
|
@@ -62,6 +63,513 @@ function getWebSocketImplementation() {
|
|
|
62
63
|
return impl;
|
|
63
64
|
}
|
|
64
65
|
//#endregion
|
|
66
|
+
//#region src/relay/auth.ts
|
|
67
|
+
var RelayAuth = class {
|
|
68
|
+
#challenge;
|
|
69
|
+
#authedChallenge;
|
|
70
|
+
/** Challenge value already answered on this connection; duplicates are not re-signed. */
|
|
71
|
+
#answeredChallenge;
|
|
72
|
+
/** Settled OK verdict for `#answeredChallenge`; set only once the relay replies. */
|
|
73
|
+
#answeredResult;
|
|
74
|
+
#authPromise;
|
|
75
|
+
#url;
|
|
76
|
+
#isOpen;
|
|
77
|
+
#sendAuth;
|
|
78
|
+
#defaultTimeoutMs;
|
|
79
|
+
constructor(deps) {
|
|
80
|
+
this.#url = deps.url;
|
|
81
|
+
this.#isOpen = deps.isOpen;
|
|
82
|
+
this.#sendAuth = deps.sendAuth;
|
|
83
|
+
this.#defaultTimeoutMs = deps.defaultTimeoutMs;
|
|
84
|
+
}
|
|
85
|
+
/** Latest NIP-42 challenge, if any. */
|
|
86
|
+
get challenge() {
|
|
87
|
+
return this.#challenge;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* A re-sent identical challenge keeps the in-flight/settled dedupe; only a new challenge value
|
|
91
|
+
* resets the answer state.
|
|
92
|
+
*/
|
|
93
|
+
handleChallenge(challenge) {
|
|
94
|
+
if (challenge !== this.#challenge) {
|
|
95
|
+
this.#authPromise = void 0;
|
|
96
|
+
this.#authedChallenge = void 0;
|
|
97
|
+
this.#answeredChallenge = void 0;
|
|
98
|
+
this.#answeredResult = void 0;
|
|
99
|
+
}
|
|
100
|
+
this.#challenge = challenge;
|
|
101
|
+
}
|
|
102
|
+
/** New connection: challenge and answer state belong to the old socket. */
|
|
103
|
+
resetConnection() {
|
|
104
|
+
this.#challenge = void 0;
|
|
105
|
+
this.#authPromise = void 0;
|
|
106
|
+
this.#authedChallenge = void 0;
|
|
107
|
+
this.#answeredChallenge = void 0;
|
|
108
|
+
this.#answeredResult = void 0;
|
|
109
|
+
}
|
|
110
|
+
/** Sign the current challenge and wait for OK. */
|
|
111
|
+
async auth(sign, opts) {
|
|
112
|
+
const challenge = this.#challenge;
|
|
113
|
+
if (challenge === void 0 || challenge === "") throw new RelayError("no AUTH challenge received from relay", this.#url);
|
|
114
|
+
if (this.#authPromise) return this.#authPromise;
|
|
115
|
+
if (this.#answeredChallenge === challenge && this.#answeredResult !== void 0) return this.#answeredResult;
|
|
116
|
+
const pending = (async () => {
|
|
117
|
+
const template = makeAuthEvent(this.#url, challenge);
|
|
118
|
+
let event;
|
|
119
|
+
try {
|
|
120
|
+
event = await sign(template);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof NoSignerError) return {
|
|
123
|
+
ok: false,
|
|
124
|
+
message: "auth: no signer"
|
|
125
|
+
};
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
if (!this.#isOpen()) throw new RelayClosedError("not connected", this.#url);
|
|
129
|
+
const result = await this.#sendAuth(event, opts?.timeoutMs ?? this.#defaultTimeoutMs);
|
|
130
|
+
if (this.#challenge === challenge) {
|
|
131
|
+
this.#answeredChallenge = challenge;
|
|
132
|
+
this.#answeredResult = result;
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
})();
|
|
136
|
+
this.#authPromise = pending;
|
|
137
|
+
try {
|
|
138
|
+
const result = await pending;
|
|
139
|
+
if (result.ok && this.#challenge === challenge) this.#authedChallenge = challenge;
|
|
140
|
+
return result;
|
|
141
|
+
} finally {
|
|
142
|
+
if (this.#authPromise === pending) this.#authPromise = void 0;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async ensureAuthed(signer) {
|
|
146
|
+
if (!signer) return false;
|
|
147
|
+
for (let i = 0; i < 3; i++) {
|
|
148
|
+
if (this.#challenge === void 0 || this.#challenge === "") return false;
|
|
149
|
+
if (this.#authedChallenge === this.#challenge) return true;
|
|
150
|
+
const signed = this.#challenge;
|
|
151
|
+
const result = await this.auth(signer);
|
|
152
|
+
if (this.#authedChallenge === this.#challenge) return true;
|
|
153
|
+
if (!result.ok) {
|
|
154
|
+
if (this.#challenge === void 0 || this.#challenge === "" || this.#challenge === signed) return false;
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return this.#authedChallenge === this.#challenge;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Clear a cached AUTH rejection for the current challenge (a successful auth and in-flight
|
|
162
|
+
* answers are kept) and return the unanswered challenge to re-emit, if any.
|
|
163
|
+
*/
|
|
164
|
+
resetRejection() {
|
|
165
|
+
if (this.#answeredResult !== void 0 && !this.#answeredResult.ok) {
|
|
166
|
+
this.#answeredChallenge = void 0;
|
|
167
|
+
this.#answeredResult = void 0;
|
|
168
|
+
}
|
|
169
|
+
if (this.#challenge !== void 0 && this.#challenge !== this.#authedChallenge) return this.#challenge;
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
//#endregion
|
|
173
|
+
//#region src/relay/count-tracker.ts
|
|
174
|
+
/**
|
|
175
|
+
* COUNT request correlation: one waiter per request id with a timeout, abort cleanup and a re-send
|
|
176
|
+
* after AUTH.
|
|
177
|
+
*/
|
|
178
|
+
var CountTracker = class {
|
|
179
|
+
#entries = /* @__PURE__ */ new Map();
|
|
180
|
+
get size() {
|
|
181
|
+
return this.#entries.size;
|
|
182
|
+
}
|
|
183
|
+
get(id) {
|
|
184
|
+
return this.#entries.get(id);
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Register a waiter, wire the abort listener, arm its timeout and call `send`. A send throw
|
|
188
|
+
* rejects (`sendError` when the throw is not an Error). resolve/reject unwind the timer, abort
|
|
189
|
+
* listener and map entry.
|
|
190
|
+
*/
|
|
191
|
+
async track(id, opts) {
|
|
192
|
+
return new Promise((resolve, reject) => {
|
|
193
|
+
const waiter = {
|
|
194
|
+
id,
|
|
195
|
+
resolve: (result) => {
|
|
196
|
+
this.#finish(waiter);
|
|
197
|
+
cleanup();
|
|
198
|
+
resolve(result);
|
|
199
|
+
},
|
|
200
|
+
reject: (err) => {
|
|
201
|
+
this.#finish(waiter);
|
|
202
|
+
cleanup();
|
|
203
|
+
reject(err);
|
|
204
|
+
},
|
|
205
|
+
timer: void 0,
|
|
206
|
+
filters: opts.filters,
|
|
207
|
+
authRetried: false,
|
|
208
|
+
timeoutMs: opts.timeoutMs,
|
|
209
|
+
timeoutError: opts.timeoutError
|
|
210
|
+
};
|
|
211
|
+
const cleanup = onAbort(opts.signal, () => {
|
|
212
|
+
if (opts.signal !== void 0) waiter.reject(abortReason(opts.signal));
|
|
213
|
+
});
|
|
214
|
+
waiter.timer = setTimeout(() => {
|
|
215
|
+
waiter.reject(opts.timeoutError());
|
|
216
|
+
}, opts.timeoutMs);
|
|
217
|
+
this.#entries.set(id, waiter);
|
|
218
|
+
try {
|
|
219
|
+
opts.send();
|
|
220
|
+
} catch (error) {
|
|
221
|
+
waiter.reject(error instanceof Error ? error : opts.sendError());
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
/** Stop a waiter's timeout without settling (AUTH retry window). */
|
|
226
|
+
clearTimer(waiter) {
|
|
227
|
+
if (waiter.timer !== void 0) {
|
|
228
|
+
clearTimeout(waiter.timer);
|
|
229
|
+
waiter.timer = void 0;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/** Re-arm the timeout for a re-sent COUNT (after an AUTH retry). */
|
|
233
|
+
restartTimer(id) {
|
|
234
|
+
const waiter = this.#entries.get(id);
|
|
235
|
+
if (waiter === void 0) return;
|
|
236
|
+
waiter.timer = setTimeout(() => {
|
|
237
|
+
waiter.reject(waiter.timeoutError());
|
|
238
|
+
}, waiter.timeoutMs);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Re-send a COUNT after the relay demanded AUTH: pause the timeout through the AUTH window,
|
|
242
|
+
* re-arm it and re-send on success, reject on failure.
|
|
243
|
+
*/
|
|
244
|
+
async authRetry(waiter, deps) {
|
|
245
|
+
this.clearTimer(waiter);
|
|
246
|
+
try {
|
|
247
|
+
const ok = await deps.ensureAuthed();
|
|
248
|
+
if (this.#entries.get(waiter.id) !== waiter) return;
|
|
249
|
+
if (!ok || !deps.isOpen()) {
|
|
250
|
+
waiter.reject(deps.failError());
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
this.restartTimer(waiter.id);
|
|
254
|
+
deps.send();
|
|
255
|
+
} catch (error) {
|
|
256
|
+
if (this.#entries.get(waiter.id) === void 0) return;
|
|
257
|
+
waiter.reject(error instanceof Error ? error : deps.failError());
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
rejectAll(err) {
|
|
261
|
+
for (const waiter of this.#entries.values()) {
|
|
262
|
+
this.clearTimer(waiter);
|
|
263
|
+
waiter.reject(err);
|
|
264
|
+
}
|
|
265
|
+
this.#entries.clear();
|
|
266
|
+
}
|
|
267
|
+
#finish(waiter) {
|
|
268
|
+
this.clearTimer(waiter);
|
|
269
|
+
this.#entries.delete(waiter.id);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region src/relay/subscription.ts
|
|
274
|
+
/**
|
|
275
|
+
* Internal REQ runtime: owns its lifecycle flags, watermark bookkeeping and handler dispatch.
|
|
276
|
+
* `onLocalClose` is supplied by the registry — it sends CLOSE / detaches from a live group — and is
|
|
277
|
+
* invoked by {@link close} only, never by {@link end}.
|
|
278
|
+
*/
|
|
279
|
+
var Subscription = class {
|
|
280
|
+
id;
|
|
281
|
+
filters;
|
|
282
|
+
closeOnEose;
|
|
283
|
+
#handlers;
|
|
284
|
+
#onLocalClose;
|
|
285
|
+
#abortDispose;
|
|
286
|
+
#eoseTimer;
|
|
287
|
+
#closed = false;
|
|
288
|
+
#eosed = false;
|
|
289
|
+
/** True after one CLOSED `auth-required:` retry. */
|
|
290
|
+
#authRetried = false;
|
|
291
|
+
/** Inclusive NIP-01 `since` watermark from verified EVENTs. */
|
|
292
|
+
#lastCreatedAt;
|
|
293
|
+
/** Event ids at `lastCreatedAt` (same-second reconnect dedup). Not all seen ids. */
|
|
294
|
+
#idsAtWatermark = /* @__PURE__ */ new Set();
|
|
295
|
+
constructor(filters, opts, onLocalClose) {
|
|
296
|
+
this.#onLocalClose = onLocalClose;
|
|
297
|
+
this.id = createSubscriptionId(opts.id);
|
|
298
|
+
this.filters = filters;
|
|
299
|
+
this.closeOnEose = opts.closeOnEose === true;
|
|
300
|
+
this.#handlers = {
|
|
301
|
+
onevent: opts.onevent,
|
|
302
|
+
oneose: opts.oneose,
|
|
303
|
+
onclose: opts.onclose,
|
|
304
|
+
alreadyHaveEvent: opts.alreadyHaveEvent,
|
|
305
|
+
receivedEvent: opts.receivedEvent
|
|
306
|
+
};
|
|
307
|
+
if (opts.eoseTimeoutMs !== void 0) this.#eoseTimer = setTimeout(() => {
|
|
308
|
+
this.#eoseTimer = void 0;
|
|
309
|
+
this.markEose();
|
|
310
|
+
}, opts.eoseTimeoutMs);
|
|
311
|
+
if (opts.signal) {
|
|
312
|
+
if (opts.signal.aborted) this.close("aborted");
|
|
313
|
+
else this.#abortDispose = onAbort(opts.signal, () => this.close("aborted"));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
get closed() {
|
|
317
|
+
return this.#closed;
|
|
318
|
+
}
|
|
319
|
+
get eosed() {
|
|
320
|
+
return this.#eosed;
|
|
321
|
+
}
|
|
322
|
+
get lastCreatedAt() {
|
|
323
|
+
return this.#lastCreatedAt;
|
|
324
|
+
}
|
|
325
|
+
get idsAtWatermark() {
|
|
326
|
+
return this.#idsAtWatermark;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Local close: idempotent; clears the EOSE timer and abort listener, detaches via `onLocalClose`
|
|
330
|
+
* (sends CLOSE / leaves the live group), then fires `onclose`.
|
|
331
|
+
*/
|
|
332
|
+
close(reason = "closed by client") {
|
|
333
|
+
if (this.#closed) return;
|
|
334
|
+
this.#closed = true;
|
|
335
|
+
this.#teardown();
|
|
336
|
+
this.#onLocalClose(this);
|
|
337
|
+
invokeSafely(() => this.#handlers.onclose?.(reason));
|
|
338
|
+
}
|
|
339
|
+
/** Remote/transport termination: like {@link close} but `onLocalClose` is not called. */
|
|
340
|
+
end(reason) {
|
|
341
|
+
if (this.#closed) return;
|
|
342
|
+
this.#closed = true;
|
|
343
|
+
this.#teardown();
|
|
344
|
+
invokeSafely(() => this.#handlers.onclose?.(reason));
|
|
345
|
+
}
|
|
346
|
+
#teardown() {
|
|
347
|
+
if (this.#eoseTimer !== void 0) {
|
|
348
|
+
clearTimeout(this.#eoseTimer);
|
|
349
|
+
this.#eoseTimer = void 0;
|
|
350
|
+
}
|
|
351
|
+
this.#abortDispose?.();
|
|
352
|
+
this.#abortDispose = void 0;
|
|
353
|
+
}
|
|
354
|
+
/** Mark EOSE: fires `oneose`, then closes when `closeOnEose`. No-op when closed or EOSE'd. */
|
|
355
|
+
markEose() {
|
|
356
|
+
if (this.#closed || this.#eosed) return;
|
|
357
|
+
this.#eosed = true;
|
|
358
|
+
if (this.#eoseTimer !== void 0) {
|
|
359
|
+
clearTimeout(this.#eoseTimer);
|
|
360
|
+
this.#eoseTimer = void 0;
|
|
361
|
+
}
|
|
362
|
+
invokeSafely(() => this.#handlers.oneose?.());
|
|
363
|
+
if (this.closeOnEose) this.close("eose");
|
|
364
|
+
}
|
|
365
|
+
/** Clear `eosed` for a replayed REQ (AUTH retry / reconnect). */
|
|
366
|
+
rearm() {
|
|
367
|
+
this.#eosed = false;
|
|
368
|
+
}
|
|
369
|
+
/** First `auth-required:` retry wins; later calls report false without side effects. */
|
|
370
|
+
beginAuthRetry() {
|
|
371
|
+
if (this.#authRetried) return false;
|
|
372
|
+
this.#authRetried = true;
|
|
373
|
+
return true;
|
|
374
|
+
}
|
|
375
|
+
resetAuthRetry() {
|
|
376
|
+
this.#authRetried = false;
|
|
377
|
+
}
|
|
378
|
+
/** `receivedEvent` for every EVENT id, before watermark/alreadyHaveEvent/verify. */
|
|
379
|
+
notifyReceived(id) {
|
|
380
|
+
invokeSafely(() => this.#handlers.receivedEvent?.(id));
|
|
381
|
+
}
|
|
382
|
+
alreadyHas(id) {
|
|
383
|
+
let have = false;
|
|
384
|
+
invokeSafely(() => {
|
|
385
|
+
have = Boolean(this.#handlers.alreadyHaveEvent?.(id));
|
|
386
|
+
});
|
|
387
|
+
return have;
|
|
388
|
+
}
|
|
389
|
+
isAtWatermark(id) {
|
|
390
|
+
return this.#idsAtWatermark.has(id);
|
|
391
|
+
}
|
|
392
|
+
/** Verified event: advance the watermark, then fire `onevent`. */
|
|
393
|
+
deliver(event) {
|
|
394
|
+
this.noteVerified(event);
|
|
395
|
+
invokeSafely(() => this.#handlers.onevent?.(event));
|
|
396
|
+
}
|
|
397
|
+
/** Advance the reconnect watermark after a verified EVENT. */
|
|
398
|
+
noteVerified(event) {
|
|
399
|
+
if (this.#lastCreatedAt === void 0 || event.created_at > this.#lastCreatedAt) {
|
|
400
|
+
this.#lastCreatedAt = event.created_at;
|
|
401
|
+
this.#idsAtWatermark.clear();
|
|
402
|
+
this.#idsAtWatermark.add(event.id);
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if (event.created_at === this.#lastCreatedAt) this.#idsAtWatermark.add(event.id);
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Filters for re-REQ. Original `filters` stay unchanged. NIP-01 `since` is inclusive — never
|
|
409
|
+
* `lastCreatedAt + 1`.
|
|
410
|
+
*/
|
|
411
|
+
replayFilters() {
|
|
412
|
+
const since = this.#lastCreatedAt;
|
|
413
|
+
if (since === void 0) return [...this.filters];
|
|
414
|
+
return this.filters.map((f) => ({
|
|
415
|
+
...f,
|
|
416
|
+
since: f.since === void 0 ? since : Math.max(f.since, since)
|
|
417
|
+
}));
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
/** AsyncIterable wrapper over a subscription's events until EOSE or close. */
|
|
421
|
+
function subscriptionToAsyncIterable(start, opts) {
|
|
422
|
+
const queue = [];
|
|
423
|
+
let head = 0;
|
|
424
|
+
let done = false;
|
|
425
|
+
let error;
|
|
426
|
+
let wake;
|
|
427
|
+
let closer;
|
|
428
|
+
let localClose = false;
|
|
429
|
+
let disposeAbort;
|
|
430
|
+
const notify = () => {
|
|
431
|
+
wake?.();
|
|
432
|
+
wake = void 0;
|
|
433
|
+
};
|
|
434
|
+
const waitForWake = async () => new Promise((resolve) => {
|
|
435
|
+
wake = resolve;
|
|
436
|
+
});
|
|
437
|
+
const closeLocal = (reason) => {
|
|
438
|
+
localClose = true;
|
|
439
|
+
disposeAbort?.();
|
|
440
|
+
closer?.close(reason);
|
|
441
|
+
done = true;
|
|
442
|
+
notify();
|
|
443
|
+
};
|
|
444
|
+
closer = start({
|
|
445
|
+
onevent(event) {
|
|
446
|
+
queue.push(event);
|
|
447
|
+
notify();
|
|
448
|
+
},
|
|
449
|
+
oneose() {
|
|
450
|
+
if (opts?.includeEose === false) closeLocal("eose");
|
|
451
|
+
},
|
|
452
|
+
onclose(reason) {
|
|
453
|
+
if (opts?.signal?.aborted === true) localClose = true;
|
|
454
|
+
if (!localClose && reason) error = new RelayClosedError(reason);
|
|
455
|
+
done = true;
|
|
456
|
+
notify();
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
if (opts?.signal) {
|
|
460
|
+
if (opts.signal.aborted) closeLocal("aborted");
|
|
461
|
+
else {
|
|
462
|
+
disposeAbort = onAbort(opts.signal, () => closeLocal("aborted"));
|
|
463
|
+
if (done) disposeAbort();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
return {
|
|
467
|
+
close(reason) {
|
|
468
|
+
closeLocal(reason ?? "closed by client");
|
|
469
|
+
},
|
|
470
|
+
[Symbol.asyncIterator]() {
|
|
471
|
+
return {
|
|
472
|
+
async next() {
|
|
473
|
+
while (true) {
|
|
474
|
+
const value = head < queue.length ? queue[head] : void 0;
|
|
475
|
+
if (value !== void 0) {
|
|
476
|
+
head += 1;
|
|
477
|
+
if (head === queue.length) {
|
|
478
|
+
queue.length = 0;
|
|
479
|
+
head = 0;
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
value,
|
|
483
|
+
done: false
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
if (error !== void 0) throw error;
|
|
487
|
+
if (done) return {
|
|
488
|
+
value: void 0,
|
|
489
|
+
done: true
|
|
490
|
+
};
|
|
491
|
+
await waitForWake();
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
async return() {
|
|
495
|
+
closeLocal("iterator returned");
|
|
496
|
+
return {
|
|
497
|
+
value: void 0,
|
|
498
|
+
done: true
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
//#endregion
|
|
506
|
+
//#region src/relay/fetch.ts
|
|
507
|
+
function streamFilters(subscribe, filters, opts) {
|
|
508
|
+
return subscriptionToAsyncIterable((handlers) => subscribe(filters, {
|
|
509
|
+
...handlers,
|
|
510
|
+
id: opts?.id,
|
|
511
|
+
signal: opts?.signal
|
|
512
|
+
}), { signal: opts?.signal });
|
|
513
|
+
}
|
|
514
|
+
async function fetchFilters(subscribe, filters, opts) {
|
|
515
|
+
throwIfAborted(opts.signal);
|
|
516
|
+
const events = [];
|
|
517
|
+
const seen = /* @__PURE__ */ new Set();
|
|
518
|
+
let end = { type: "timeout" };
|
|
519
|
+
await new Promise((resolve, reject) => {
|
|
520
|
+
let settled = false;
|
|
521
|
+
const done = (err) => {
|
|
522
|
+
if (settled) return;
|
|
523
|
+
settled = true;
|
|
524
|
+
disposeAbort();
|
|
525
|
+
clearTimeout(timer);
|
|
526
|
+
let reason = "aborted";
|
|
527
|
+
if (err instanceof Error) reason = err.message;
|
|
528
|
+
else if (err === void 0) reason = "fetch complete";
|
|
529
|
+
sub.close(reason);
|
|
530
|
+
if (err === void 0) resolve();
|
|
531
|
+
else reject(err);
|
|
532
|
+
};
|
|
533
|
+
const disposeAbort = onAbort(opts.signal, () => {
|
|
534
|
+
if (opts.signal !== void 0) done(abortReason(opts.signal));
|
|
535
|
+
});
|
|
536
|
+
const timer = setTimeout(() => {
|
|
537
|
+
end = { type: "timeout" };
|
|
538
|
+
done();
|
|
539
|
+
}, opts.timeoutMs);
|
|
540
|
+
const sub = subscribe(filters, {
|
|
541
|
+
id: opts.id,
|
|
542
|
+
signal: opts.signal,
|
|
543
|
+
closeOnEose: true,
|
|
544
|
+
onevent(event) {
|
|
545
|
+
if (seen.has(event.id)) return;
|
|
546
|
+
seen.add(event.id);
|
|
547
|
+
events.push(event);
|
|
548
|
+
},
|
|
549
|
+
oneose() {
|
|
550
|
+
end = { type: "eose" };
|
|
551
|
+
done();
|
|
552
|
+
},
|
|
553
|
+
onclose(reason) {
|
|
554
|
+
if (settled) return;
|
|
555
|
+
if (opts.signal?.aborted === true) {
|
|
556
|
+
done(abortReason(opts.signal));
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
559
|
+
end = {
|
|
560
|
+
type: "closed",
|
|
561
|
+
reason
|
|
562
|
+
};
|
|
563
|
+
done();
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
return {
|
|
568
|
+
events,
|
|
569
|
+
end
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
//#endregion
|
|
65
573
|
//#region src/relay/neg-session.ts
|
|
66
574
|
function createNegSession() {
|
|
67
575
|
return {
|
|
@@ -88,6 +596,21 @@ function failNegSession(session, err) {
|
|
|
88
596
|
function failNegErr(session, reason) {
|
|
89
597
|
failNegSession(session, new Nip77Error(reason));
|
|
90
598
|
}
|
|
599
|
+
/** Route a NEG-MSG/NEG-ERR frame to its session; unknown ids are ignored. */
|
|
600
|
+
function dispatchNegMessage(sessions, msg) {
|
|
601
|
+
const session = sessions.get(msg[1]);
|
|
602
|
+
if (session === void 0) return;
|
|
603
|
+
if (msg[0] === "NEG-MSG") {
|
|
604
|
+
pushNegMsg(session, msg[2]);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
failNegErr(session, msg[2]);
|
|
608
|
+
}
|
|
609
|
+
/** Fail every tracked session (socket teardown). */
|
|
610
|
+
function failAllNegSessions(sessions, err) {
|
|
611
|
+
for (const session of sessions.values()) failNegSession(session, err);
|
|
612
|
+
sessions.clear();
|
|
613
|
+
}
|
|
91
614
|
/** Wire queue/timeout/abort around `runNegSession`. Not a second session class. */
|
|
92
615
|
async function runWiredNegSession(opts) {
|
|
93
616
|
const { session, storage, filter, id, timeoutMs, signal, send, url } = opts;
|
|
@@ -144,6 +667,175 @@ async function runWiredNegSession(opts) {
|
|
|
144
667
|
next
|
|
145
668
|
});
|
|
146
669
|
}
|
|
670
|
+
/**
|
|
671
|
+
* Drive a NIP-77 reconcile session against the session map: fail a same-id predecessor, register,
|
|
672
|
+
* wire queue/timeout/abort, and send NEG-CLOSE on exit.
|
|
673
|
+
*/
|
|
674
|
+
async function runTrackedNegSession(opts) {
|
|
675
|
+
const { sessions, id, send } = opts;
|
|
676
|
+
const prev = sessions.get(id);
|
|
677
|
+
if (prev) failNegSession(prev, new Nip77Error("closed: replaced by new NEG-OPEN"));
|
|
678
|
+
const session = createNegSession();
|
|
679
|
+
sessions.set(id, session);
|
|
680
|
+
try {
|
|
681
|
+
return await runWiredNegSession({
|
|
682
|
+
session,
|
|
683
|
+
storage: opts.storage,
|
|
684
|
+
filter: opts.filter,
|
|
685
|
+
id,
|
|
686
|
+
timeoutMs: opts.timeoutMs,
|
|
687
|
+
signal: opts.signal,
|
|
688
|
+
send,
|
|
689
|
+
url: opts.url
|
|
690
|
+
});
|
|
691
|
+
} finally {
|
|
692
|
+
if (sessions.get(id) === session) {
|
|
693
|
+
sessions.delete(id);
|
|
694
|
+
try {
|
|
695
|
+
send(["NEG-CLOSE", id]);
|
|
696
|
+
} catch {}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
//#endregion
|
|
701
|
+
//#region src/relay/ok-tracker.ts
|
|
702
|
+
var OkTracker = class {
|
|
703
|
+
#entries = /* @__PURE__ */ new Map();
|
|
704
|
+
get size() {
|
|
705
|
+
return this.#entries.size;
|
|
706
|
+
}
|
|
707
|
+
get(eventId) {
|
|
708
|
+
return this.#entries.get(eventId);
|
|
709
|
+
}
|
|
710
|
+
/** Joiner's view: only publish waiters (carrying an `event`) are joinable. */
|
|
711
|
+
inFlight(eventId) {
|
|
712
|
+
const waiter = this.#entries.get(eventId);
|
|
713
|
+
return waiter?.event === void 0 ? void 0 : waiter.promise;
|
|
714
|
+
}
|
|
715
|
+
/**
|
|
716
|
+
* Register a waiter, arm its timeout and call `send`. A send throw removes the entry and rejects
|
|
717
|
+
* (`sendError` when the throw is not an Error). The timer only rejects while this waiter still
|
|
718
|
+
* owns the id.
|
|
719
|
+
*/
|
|
720
|
+
async track(eventId, opts) {
|
|
721
|
+
const promise = new Promise((resolve, reject) => {
|
|
722
|
+
const waiter = {
|
|
723
|
+
id: eventId,
|
|
724
|
+
resolve,
|
|
725
|
+
reject,
|
|
726
|
+
timer: setTimeout(() => {
|
|
727
|
+
if (this.#entries.get(eventId) === waiter) {
|
|
728
|
+
this.#entries.delete(eventId);
|
|
729
|
+
reject(opts.timeoutError());
|
|
730
|
+
}
|
|
731
|
+
}, opts.timeoutMs),
|
|
732
|
+
event: opts.event,
|
|
733
|
+
authRetried: false,
|
|
734
|
+
timeoutMs: opts.timeoutMs,
|
|
735
|
+
timeoutError: opts.timeoutError,
|
|
736
|
+
get promise() {
|
|
737
|
+
return promise;
|
|
738
|
+
}
|
|
739
|
+
};
|
|
740
|
+
this.#entries.set(eventId, waiter);
|
|
741
|
+
try {
|
|
742
|
+
opts.send();
|
|
743
|
+
} catch (error) {
|
|
744
|
+
this.#finish(waiter);
|
|
745
|
+
reject(error instanceof Error ? error : opts.sendError());
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
return promise;
|
|
749
|
+
}
|
|
750
|
+
/**
|
|
751
|
+
* An OK reply arrived: `retry` is the caller's auth-required policy — a kept waiter is returned
|
|
752
|
+
* unsettled for the retry path; otherwise the entry is settled and undefined is returned.
|
|
753
|
+
*/
|
|
754
|
+
handleOk(eventId, ok, message, retry) {
|
|
755
|
+
const waiter = this.#entries.get(eventId);
|
|
756
|
+
if (waiter === void 0) return;
|
|
757
|
+
if (!retry(waiter)) {
|
|
758
|
+
this.settle(waiter, {
|
|
759
|
+
ok,
|
|
760
|
+
message
|
|
761
|
+
});
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
return waiter;
|
|
765
|
+
}
|
|
766
|
+
/** Settle a waiter: stop its timer, drop the entry it still owns, then resolve. */
|
|
767
|
+
settle(waiter, result) {
|
|
768
|
+
this.#finish(waiter);
|
|
769
|
+
waiter.resolve(result);
|
|
770
|
+
}
|
|
771
|
+
/** Stop a waiter's timeout without settling (AUTH retry window). */
|
|
772
|
+
clearTimer(waiter) {
|
|
773
|
+
if (waiter.timer !== void 0) {
|
|
774
|
+
clearTimeout(waiter.timer);
|
|
775
|
+
waiter.timer = void 0;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
/** Re-arm the timeout for a re-sent frame (after an AUTH retry). */
|
|
779
|
+
restartTimer(eventId) {
|
|
780
|
+
const waiter = this.#entries.get(eventId);
|
|
781
|
+
if (waiter === void 0) return;
|
|
782
|
+
waiter.timer = setTimeout(() => {
|
|
783
|
+
if (this.#entries.get(eventId) === waiter) {
|
|
784
|
+
this.#entries.delete(eventId);
|
|
785
|
+
waiter.reject(waiter.timeoutError());
|
|
786
|
+
}
|
|
787
|
+
}, waiter.timeoutMs);
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Re-send a tracked EVENT after the relay demanded AUTH: pause the timeout through the AUTH
|
|
791
|
+
* window, re-arm it and re-send on success, settle `{ok:false,message}` on failure. AUTH waiters
|
|
792
|
+
* (no `event`) settle immediately.
|
|
793
|
+
*/
|
|
794
|
+
async authRetry(waiter, message, deps) {
|
|
795
|
+
this.clearTimer(waiter);
|
|
796
|
+
const finish = (result) => {
|
|
797
|
+
this.settle(waiter, result);
|
|
798
|
+
};
|
|
799
|
+
try {
|
|
800
|
+
const { event } = waiter;
|
|
801
|
+
if (event === void 0) {
|
|
802
|
+
finish({
|
|
803
|
+
ok: false,
|
|
804
|
+
message
|
|
805
|
+
});
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const ok = await deps.ensureAuthed();
|
|
809
|
+
if (this.#entries.get(waiter.id) !== waiter) return;
|
|
810
|
+
if (!ok || !deps.isOpen()) {
|
|
811
|
+
finish({
|
|
812
|
+
ok: false,
|
|
813
|
+
message
|
|
814
|
+
});
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
this.restartTimer(waiter.id);
|
|
818
|
+
deps.send(event);
|
|
819
|
+
} catch {
|
|
820
|
+
if (this.#entries.get(waiter.id) === void 0) return;
|
|
821
|
+
finish({
|
|
822
|
+
ok: false,
|
|
823
|
+
message
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
rejectAll(err) {
|
|
828
|
+
for (const waiter of this.#entries.values()) {
|
|
829
|
+
this.clearTimer(waiter);
|
|
830
|
+
waiter.reject(err);
|
|
831
|
+
}
|
|
832
|
+
this.#entries.clear();
|
|
833
|
+
}
|
|
834
|
+
#finish(waiter) {
|
|
835
|
+
this.clearTimer(waiter);
|
|
836
|
+
if (this.#entries.get(waiter.id) === waiter) this.#entries.delete(waiter.id);
|
|
837
|
+
}
|
|
838
|
+
};
|
|
147
839
|
const PING_DUMMY_FILTER = {
|
|
148
840
|
ids: ["a".repeat(64)],
|
|
149
841
|
limit: 0
|
|
@@ -266,458 +958,252 @@ var PingLoop = class {
|
|
|
266
958
|
}
|
|
267
959
|
};
|
|
268
960
|
//#endregion
|
|
269
|
-
//#region src/relay/subscription.ts
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
this.#
|
|
287
|
-
this
|
|
288
|
-
this
|
|
289
|
-
this.closeOnEose = opts.closeOnEose === true;
|
|
290
|
-
this.handlers = {
|
|
291
|
-
onevent: opts.onevent,
|
|
292
|
-
oneose: opts.oneose,
|
|
293
|
-
onclose: opts.onclose,
|
|
294
|
-
alreadyHaveEvent: opts.alreadyHaveEvent,
|
|
295
|
-
receivedEvent: opts.receivedEvent
|
|
296
|
-
};
|
|
297
|
-
if (opts.signal) {
|
|
298
|
-
if (opts.signal.aborted) this.close("aborted");
|
|
299
|
-
else this.#abort = onAbort(opts.signal, () => this.close("aborted"));
|
|
300
|
-
}
|
|
961
|
+
//#region src/relay/subscription-registry.ts
|
|
962
|
+
/**
|
|
963
|
+
* Owns every open REQ of a relay: exclusive subscriptions keyed by id, and live coalescing groups
|
|
964
|
+
* (one wire REQ plus per-subscriber handles) keyed by fingerprint and wire id.
|
|
965
|
+
*/
|
|
966
|
+
var SubscriptionRegistry = class {
|
|
967
|
+
#subs = /* @__PURE__ */ new Map();
|
|
968
|
+
#liveByFp = /* @__PURE__ */ new Map();
|
|
969
|
+
#liveBySubId = /* @__PURE__ */ new Map();
|
|
970
|
+
#isOpen;
|
|
971
|
+
#enableReconnect;
|
|
972
|
+
#send;
|
|
973
|
+
#scheduleReconnect;
|
|
974
|
+
#acceptEvent;
|
|
975
|
+
constructor(deps) {
|
|
976
|
+
this.#isOpen = deps.isOpen;
|
|
977
|
+
this.#enableReconnect = deps.enableReconnect;
|
|
978
|
+
this.#send = deps.send;
|
|
979
|
+
this.#scheduleReconnect = deps.scheduleReconnect;
|
|
980
|
+
this.#acceptEvent = deps.acceptEvent;
|
|
301
981
|
}
|
|
302
|
-
/**
|
|
303
|
-
|
|
304
|
-
this.#
|
|
982
|
+
/** User subscriptions only; dummy ping REQs are not counted. */
|
|
983
|
+
get size() {
|
|
984
|
+
return this.#subs.size;
|
|
305
985
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
this.closed = true;
|
|
309
|
-
this.dispose();
|
|
310
|
-
this.#sendClose(this.id);
|
|
311
|
-
invokeSafely(() => this.handlers.onclose?.(reason));
|
|
986
|
+
get(subId) {
|
|
987
|
+
return this.#subs.get(subId);
|
|
312
988
|
}
|
|
313
|
-
/**
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
989
|
+
/** Exclusive one-shot REQ when `closeOnEose`, live coalescing otherwise. */
|
|
990
|
+
open(filters, opts) {
|
|
991
|
+
if (opts.closeOnEose === true) return this.#openExclusive(filters, opts);
|
|
992
|
+
return this.#openLive(filters, opts);
|
|
993
|
+
}
|
|
994
|
+
handleEvent(subId, event) {
|
|
995
|
+
const sub = this.#subs.get(subId);
|
|
996
|
+
if (!sub || sub.closed) return;
|
|
997
|
+
const group = this.#liveBySubId.get(subId);
|
|
998
|
+
if (group) {
|
|
999
|
+
this.#deliverLiveEvent(group, event);
|
|
319
1000
|
return;
|
|
320
1001
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
};
|
|
336
|
-
/** AsyncIterable wrapper over a subscription's events until EOSE or close. */
|
|
337
|
-
function subscriptionToAsyncIterable(start, opts) {
|
|
338
|
-
const queue = [];
|
|
339
|
-
let done = false;
|
|
340
|
-
let error;
|
|
341
|
-
let wake;
|
|
342
|
-
let closer;
|
|
343
|
-
let localClose = false;
|
|
344
|
-
let disposeAbort;
|
|
345
|
-
const notify = () => {
|
|
346
|
-
wake?.();
|
|
347
|
-
wake = void 0;
|
|
348
|
-
};
|
|
349
|
-
const waitForWake = async () => new Promise((resolve) => {
|
|
350
|
-
wake = resolve;
|
|
351
|
-
});
|
|
352
|
-
const closeLocal = (reason) => {
|
|
353
|
-
localClose = true;
|
|
354
|
-
disposeAbort?.();
|
|
355
|
-
closer?.close(reason);
|
|
356
|
-
done = true;
|
|
357
|
-
notify();
|
|
358
|
-
};
|
|
359
|
-
closer = start({
|
|
360
|
-
onevent(event) {
|
|
361
|
-
queue.push(event);
|
|
362
|
-
notify();
|
|
363
|
-
},
|
|
364
|
-
oneose() {
|
|
365
|
-
if (opts?.includeEose === false) closeLocal("eose");
|
|
366
|
-
},
|
|
367
|
-
onclose(reason) {
|
|
368
|
-
if (opts?.signal?.aborted === true) localClose = true;
|
|
369
|
-
if (!localClose && reason) error = new RelayClosedError(reason);
|
|
370
|
-
done = true;
|
|
371
|
-
notify();
|
|
1002
|
+
sub.notifyReceived(event.id);
|
|
1003
|
+
if (sub.isAtWatermark(event.id)) return;
|
|
1004
|
+
if (sub.alreadyHas(event.id)) return;
|
|
1005
|
+
if (!this.#acceptEvent(event)) return;
|
|
1006
|
+
sub.deliver(event);
|
|
1007
|
+
}
|
|
1008
|
+
handleEose(subId) {
|
|
1009
|
+
const sub = this.#subs.get(subId);
|
|
1010
|
+
if (!sub || sub.closed || sub.eosed) return;
|
|
1011
|
+
const group = this.#liveBySubId.get(subId);
|
|
1012
|
+
if (group) {
|
|
1013
|
+
sub.markEose();
|
|
1014
|
+
this.#deliverLiveEose(group);
|
|
1015
|
+
return;
|
|
372
1016
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
1017
|
+
sub.markEose();
|
|
1018
|
+
}
|
|
1019
|
+
/** Remote/transport termination of a REQ (CLOSED, auth-retry failure). */
|
|
1020
|
+
drop(sub, reason) {
|
|
1021
|
+
const group = this.#liveBySubId.get(sub.id);
|
|
1022
|
+
if (group) {
|
|
1023
|
+
this.#endLiveGroup(group, {
|
|
1024
|
+
sendClose: false,
|
|
1025
|
+
reason
|
|
1026
|
+
});
|
|
1027
|
+
return;
|
|
379
1028
|
}
|
|
1029
|
+
this.#subs.delete(sub.id);
|
|
1030
|
+
sub.end(reason);
|
|
380
1031
|
}
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return {
|
|
387
|
-
async next() {
|
|
388
|
-
while (true) {
|
|
389
|
-
const value = queue.shift();
|
|
390
|
-
if (value !== void 0) return {
|
|
391
|
-
value,
|
|
392
|
-
done: false
|
|
393
|
-
};
|
|
394
|
-
if (error !== void 0) throw error;
|
|
395
|
-
if (done) return {
|
|
396
|
-
value: void 0,
|
|
397
|
-
done: true
|
|
398
|
-
};
|
|
399
|
-
await waitForWake();
|
|
400
|
-
}
|
|
401
|
-
},
|
|
402
|
-
async return() {
|
|
403
|
-
closeLocal("iterator returned");
|
|
404
|
-
return {
|
|
405
|
-
value: void 0,
|
|
406
|
-
done: true
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
};
|
|
410
|
-
}
|
|
411
|
-
};
|
|
412
|
-
}
|
|
413
|
-
//#endregion
|
|
414
|
-
//#region src/relay/subscribe.ts
|
|
415
|
-
/** REQ subscription runtime: exclusive one-shot, live coalescing, dispatch, reconnect replay. */
|
|
416
|
-
function openExclusive(ctx, filters, opts) {
|
|
417
|
-
const sub = new Subscription(filters, opts, (id) => {
|
|
418
|
-
ctx.subs.delete(id);
|
|
1032
|
+
/**
|
|
1033
|
+
* Re-REQ after the relay demanded AUTH: drop the subscription when auth fails or the
|
|
1034
|
+
* socket/subscription died during the AUTH window, replay the REQ on success.
|
|
1035
|
+
*/
|
|
1036
|
+
async authRetry(sub, reason, ensureAuthed) {
|
|
419
1037
|
try {
|
|
420
|
-
if (
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
]);
|
|
429
|
-
else if (ctx.enableReconnect()) ctx.scheduleReconnect();
|
|
430
|
-
if (opts.eoseTimeoutMs !== void 0) ctx.armEoseTimeout(sub, opts.eoseTimeoutMs);
|
|
431
|
-
return sub;
|
|
432
|
-
}
|
|
433
|
-
function subscribeLive(ctx, filters, opts) {
|
|
434
|
-
const fp = filterFingerprint(filters);
|
|
435
|
-
let group = ctx.liveByFp.get(fp);
|
|
436
|
-
if (group?.sub.eosed === true) group = void 0;
|
|
437
|
-
let created = false;
|
|
438
|
-
if (!group) {
|
|
439
|
-
created = true;
|
|
440
|
-
const slot = { group: void 0 };
|
|
441
|
-
const wire = new Subscription(filters, { id: opts.id }, () => {
|
|
442
|
-
const g = slot.group;
|
|
443
|
-
if (g) endLiveGroup(ctx, g, {
|
|
444
|
-
sendClose: true,
|
|
445
|
-
reason: "closed by client"
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
group = {
|
|
449
|
-
fp,
|
|
450
|
-
sub: wire,
|
|
451
|
-
attachments: /* @__PURE__ */ new Set()
|
|
452
|
-
};
|
|
453
|
-
slot.group = group;
|
|
454
|
-
ctx.liveByFp.set(fp, group);
|
|
455
|
-
ctx.liveBySubId.set(wire.id, group);
|
|
456
|
-
ctx.subs.set(wire.id, wire);
|
|
457
|
-
}
|
|
458
|
-
const slot = { handle: void 0 };
|
|
459
|
-
const attached = group;
|
|
460
|
-
const handle = new Subscription(filters, {
|
|
461
|
-
...opts,
|
|
462
|
-
id: group.sub.id
|
|
463
|
-
}, () => {
|
|
464
|
-
const current = slot.handle;
|
|
465
|
-
if (!current) return;
|
|
466
|
-
detachLive(ctx, attached, current);
|
|
467
|
-
});
|
|
468
|
-
slot.handle = handle;
|
|
469
|
-
if (handle.closed) {
|
|
470
|
-
if (created && group.attachments.size === 0) forgetLiveGroup(ctx, group);
|
|
471
|
-
return handle;
|
|
1038
|
+
if (!await ensureAuthed() || sub.closed || !this.#isOpen()) {
|
|
1039
|
+
this.drop(sub, reason);
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
this.replay(sub);
|
|
1043
|
+
} catch {
|
|
1044
|
+
this.drop(sub, reason);
|
|
1045
|
+
}
|
|
472
1046
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
1047
|
+
/** Re-REQ after AUTH: rearm the wire and its handles, then send the replayed filters. */
|
|
1048
|
+
replay(sub) {
|
|
1049
|
+
sub.rearm();
|
|
1050
|
+
const group = this.#liveBySubId.get(sub.id);
|
|
1051
|
+
if (group) for (const att of group.attachments) att.rearm();
|
|
1052
|
+
this.#send([
|
|
476
1053
|
"REQ",
|
|
477
|
-
|
|
478
|
-
...
|
|
1054
|
+
sub.id,
|
|
1055
|
+
...sub.replayFilters()
|
|
479
1056
|
]);
|
|
480
|
-
else if (ctx.enableReconnect()) ctx.scheduleReconnect();
|
|
481
|
-
}
|
|
482
|
-
if (opts.eoseTimeoutMs !== void 0) ctx.armEoseTimeout(handle, opts.eoseTimeoutMs);
|
|
483
|
-
return handle;
|
|
484
|
-
}
|
|
485
|
-
function forgetLiveGroup(ctx, group) {
|
|
486
|
-
if (ctx.liveByFp.get(group.fp) === group) ctx.liveByFp.delete(group.fp);
|
|
487
|
-
ctx.liveBySubId.delete(group.sub.id);
|
|
488
|
-
ctx.subs.delete(group.sub.id);
|
|
489
|
-
group.sub.closed = true;
|
|
490
|
-
group.sub.dispose();
|
|
491
|
-
}
|
|
492
|
-
function detachLive(ctx, group, handle) {
|
|
493
|
-
if (!group.attachments.delete(handle)) return;
|
|
494
|
-
if (group.attachments.size > 0) return;
|
|
495
|
-
endLiveGroup(ctx, group, {
|
|
496
|
-
sendClose: true,
|
|
497
|
-
reason: "closed by client"
|
|
498
|
-
});
|
|
499
|
-
}
|
|
500
|
-
function endLiveGroup(ctx, group, opts) {
|
|
501
|
-
const { id } = group.sub;
|
|
502
|
-
const remaining = [...group.attachments];
|
|
503
|
-
group.attachments.clear();
|
|
504
|
-
forgetLiveGroup(ctx, group);
|
|
505
|
-
if (opts.sendClose) try {
|
|
506
|
-
if (ctx.connected()) ctx.send(["CLOSE", id]);
|
|
507
|
-
} catch {}
|
|
508
|
-
for (const att of remaining) invokeSafely(() => {
|
|
509
|
-
att.close(opts.reason);
|
|
510
|
-
});
|
|
511
|
-
}
|
|
512
|
-
function deliverLiveEvent(ctx, group, event) {
|
|
513
|
-
const { sub } = group;
|
|
514
|
-
const attachments = [...group.attachments];
|
|
515
|
-
for (const att of attachments) invokeSafely(() => {
|
|
516
|
-
att.handlers.receivedEvent?.(event.id);
|
|
517
|
-
});
|
|
518
|
-
if (sub.idsAtWatermark.has(event.id)) return;
|
|
519
|
-
const recipients = [];
|
|
520
|
-
for (const att of attachments) {
|
|
521
|
-
if (att.closed) continue;
|
|
522
|
-
let skip = false;
|
|
523
|
-
invokeSafely(() => {
|
|
524
|
-
skip = Boolean(att.handlers.alreadyHaveEvent?.(event.id));
|
|
525
|
-
});
|
|
526
|
-
if (!skip) recipients.push(att);
|
|
527
|
-
}
|
|
528
|
-
if (recipients.length === 0) return;
|
|
529
|
-
if (!ctx.acceptEvent(event)) return;
|
|
530
|
-
sub.noteVerified(event);
|
|
531
|
-
for (const att of recipients) att.noteVerified(event);
|
|
532
|
-
for (const att of recipients) invokeSafely(() => {
|
|
533
|
-
att.handlers.onevent?.(event);
|
|
534
|
-
});
|
|
535
|
-
}
|
|
536
|
-
function deliverLiveEose(group) {
|
|
537
|
-
const attachments = [...group.attachments];
|
|
538
|
-
for (const att of attachments) {
|
|
539
|
-
if (att.closed || att.eosed) continue;
|
|
540
|
-
att.eosed = true;
|
|
541
|
-
invokeSafely(() => {
|
|
542
|
-
att.handlers.oneose?.();
|
|
543
|
-
});
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
function closeAllSubscriptions(ctx, reason) {
|
|
547
|
-
const groups = [...ctx.liveBySubId.values()];
|
|
548
|
-
for (const group of groups) invokeSafely(() => {
|
|
549
|
-
endLiveGroup(ctx, group, {
|
|
550
|
-
sendClose: false,
|
|
551
|
-
reason
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
for (const sub of ctx.subs.values()) if (!sub.closed) {
|
|
555
|
-
sub.closed = true;
|
|
556
|
-
sub.dispose();
|
|
557
|
-
invokeSafely(() => {
|
|
558
|
-
sub.handlers.onclose?.(reason);
|
|
559
|
-
});
|
|
560
|
-
}
|
|
561
|
-
ctx.subs.clear();
|
|
562
|
-
ctx.liveByFp.clear();
|
|
563
|
-
ctx.liveBySubId.clear();
|
|
564
|
-
}
|
|
565
|
-
function dropSubscription(ctx, sub, reason) {
|
|
566
|
-
const group = ctx.liveBySubId.get(sub.id);
|
|
567
|
-
if (group) {
|
|
568
|
-
endLiveGroup(ctx, group, {
|
|
569
|
-
sendClose: false,
|
|
570
|
-
reason
|
|
571
|
-
});
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
ctx.subs.delete(sub.id);
|
|
575
|
-
sub.closed = true;
|
|
576
|
-
sub.dispose();
|
|
577
|
-
invokeSafely(() => {
|
|
578
|
-
sub.handlers.onclose?.(reason);
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
function onSubEvent(ctx, subId, event) {
|
|
582
|
-
const sub = ctx.subs.get(subId);
|
|
583
|
-
if (!sub || sub.closed) return;
|
|
584
|
-
const group = ctx.liveBySubId.get(subId);
|
|
585
|
-
if (group) {
|
|
586
|
-
deliverLiveEvent(ctx, group, event);
|
|
587
|
-
return;
|
|
588
|
-
}
|
|
589
|
-
invokeSafely(() => {
|
|
590
|
-
sub.handlers.receivedEvent?.(event.id);
|
|
591
|
-
});
|
|
592
|
-
if (sub.idsAtWatermark.has(event.id)) return;
|
|
593
|
-
let have = false;
|
|
594
|
-
invokeSafely(() => {
|
|
595
|
-
have = Boolean(sub.handlers.alreadyHaveEvent?.(event.id));
|
|
596
|
-
});
|
|
597
|
-
if (have) return;
|
|
598
|
-
if (!ctx.acceptEvent(event)) return;
|
|
599
|
-
sub.noteVerified(event);
|
|
600
|
-
invokeSafely(() => {
|
|
601
|
-
sub.handlers.onevent?.(event);
|
|
602
|
-
});
|
|
603
|
-
}
|
|
604
|
-
function onSubEose(ctx, subId) {
|
|
605
|
-
const sub = ctx.subs.get(subId);
|
|
606
|
-
if (!sub || sub.closed) return;
|
|
607
|
-
if (sub.eosed) return;
|
|
608
|
-
sub.eosed = true;
|
|
609
|
-
const group = ctx.liveBySubId.get(subId);
|
|
610
|
-
if (group) deliverLiveEose(group);
|
|
611
|
-
else {
|
|
612
|
-
invokeSafely(() => {
|
|
613
|
-
sub.handlers.oneose?.();
|
|
614
|
-
});
|
|
615
|
-
if (sub.closeOnEose) sub.close("eose");
|
|
616
1057
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1058
|
+
/** Reconnect path: rearm every open REQ and re-send; false on send failure. */
|
|
1059
|
+
replayAll() {
|
|
1060
|
+
for (const sub of this.#subs.values()) {
|
|
1061
|
+
if (sub.closed) continue;
|
|
1062
|
+
sub.rearm();
|
|
1063
|
+
sub.resetAuthRetry();
|
|
1064
|
+
const group = this.#liveBySubId.get(sub.id);
|
|
1065
|
+
if (group) for (const att of group.attachments) {
|
|
1066
|
+
att.rearm();
|
|
1067
|
+
att.resetAuthRetry();
|
|
1068
|
+
}
|
|
1069
|
+
try {
|
|
1070
|
+
this.#send([
|
|
1071
|
+
"REQ",
|
|
1072
|
+
sub.id,
|
|
1073
|
+
...sub.replayFilters()
|
|
1074
|
+
]);
|
|
1075
|
+
} catch {
|
|
1076
|
+
return false;
|
|
1077
|
+
}
|
|
633
1078
|
}
|
|
1079
|
+
return true;
|
|
634
1080
|
}
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
}), { signal: opts?.signal });
|
|
643
|
-
}
|
|
644
|
-
async function fetchFilters(subscribe, filters, opts) {
|
|
645
|
-
throwIfAborted(opts.signal);
|
|
646
|
-
const events = [];
|
|
647
|
-
const seen = /* @__PURE__ */ new Set();
|
|
648
|
-
let end = { type: "timeout" };
|
|
649
|
-
await new Promise((resolve, reject) => {
|
|
650
|
-
let settled = false;
|
|
651
|
-
const done = (err) => {
|
|
652
|
-
if (settled) return;
|
|
653
|
-
settled = true;
|
|
654
|
-
disposeAbort();
|
|
655
|
-
clearTimeout(timer);
|
|
656
|
-
let reason = "aborted";
|
|
657
|
-
if (err instanceof Error) reason = err.message;
|
|
658
|
-
else if (err === void 0) reason = "fetch complete";
|
|
659
|
-
sub.close(reason);
|
|
660
|
-
if (err === void 0) resolve();
|
|
661
|
-
else reject(err);
|
|
662
|
-
};
|
|
663
|
-
const disposeAbort = onAbort(opts.signal, () => {
|
|
664
|
-
if (opts.signal !== void 0) done(abortReason(opts.signal));
|
|
1081
|
+
closeAll(reason) {
|
|
1082
|
+
const groups = [...this.#liveBySubId.values()];
|
|
1083
|
+
for (const group of groups) invokeSafely(() => {
|
|
1084
|
+
this.#endLiveGroup(group, {
|
|
1085
|
+
sendClose: false,
|
|
1086
|
+
reason
|
|
1087
|
+
});
|
|
665
1088
|
});
|
|
666
|
-
const
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
if (
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
}
|
|
1089
|
+
for (const sub of this.#subs.values()) sub.end(reason);
|
|
1090
|
+
this.#subs.clear();
|
|
1091
|
+
this.#liveByFp.clear();
|
|
1092
|
+
this.#liveBySubId.clear();
|
|
1093
|
+
}
|
|
1094
|
+
#openExclusive(filters, opts) {
|
|
1095
|
+
const sub = new Subscription(filters, opts, (closed) => {
|
|
1096
|
+
this.#subs.delete(closed.id);
|
|
1097
|
+
try {
|
|
1098
|
+
if (this.#isOpen()) this.#send(["CLOSE", closed.id]);
|
|
1099
|
+
} catch {}
|
|
1100
|
+
});
|
|
1101
|
+
this.#subs.set(sub.id, sub);
|
|
1102
|
+
if (this.#isOpen()) this.#send([
|
|
1103
|
+
"REQ",
|
|
1104
|
+
sub.id,
|
|
1105
|
+
...filters
|
|
1106
|
+
]);
|
|
1107
|
+
else if (this.#enableReconnect) this.#scheduleReconnect();
|
|
1108
|
+
return sub;
|
|
1109
|
+
}
|
|
1110
|
+
#openLive(filters, opts) {
|
|
1111
|
+
const fp = filterFingerprint(filters);
|
|
1112
|
+
let group = this.#liveByFp.get(fp);
|
|
1113
|
+
if (group?.sub.eosed === true) group = void 0;
|
|
1114
|
+
let created = false;
|
|
1115
|
+
if (!group) {
|
|
1116
|
+
created = true;
|
|
1117
|
+
const slot = { group: void 0 };
|
|
1118
|
+
const wire = new Subscription(filters, { id: opts.id }, () => {
|
|
1119
|
+
const g = slot.group;
|
|
1120
|
+
if (g) this.#endLiveGroup(g, {
|
|
1121
|
+
sendClose: true,
|
|
1122
|
+
reason: "closed by client"
|
|
1123
|
+
});
|
|
1124
|
+
});
|
|
1125
|
+
group = {
|
|
1126
|
+
fp,
|
|
1127
|
+
sub: wire,
|
|
1128
|
+
attachments: /* @__PURE__ */ new Set()
|
|
1129
|
+
};
|
|
1130
|
+
slot.group = group;
|
|
1131
|
+
this.#liveByFp.set(fp, group);
|
|
1132
|
+
this.#liveBySubId.set(wire.id, group);
|
|
1133
|
+
this.#subs.set(wire.id, wire);
|
|
1134
|
+
}
|
|
1135
|
+
const slot = { handle: void 0 };
|
|
1136
|
+
const attached = group;
|
|
1137
|
+
const handle = new Subscription(filters, {
|
|
1138
|
+
...opts,
|
|
1139
|
+
id: group.sub.id
|
|
1140
|
+
}, () => {
|
|
1141
|
+
const current = slot.handle;
|
|
1142
|
+
if (!current) return;
|
|
1143
|
+
this.#detachLive(attached, current);
|
|
695
1144
|
});
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1145
|
+
slot.handle = handle;
|
|
1146
|
+
if (handle.closed) {
|
|
1147
|
+
if (created && group.attachments.size === 0) this.#forgetLiveGroup(group, "closed by client");
|
|
1148
|
+
return handle;
|
|
1149
|
+
}
|
|
1150
|
+
group.attachments.add(handle);
|
|
1151
|
+
if (created) {
|
|
1152
|
+
if (this.#isOpen()) this.#send([
|
|
1153
|
+
"REQ",
|
|
1154
|
+
group.sub.id,
|
|
1155
|
+
...filters
|
|
1156
|
+
]);
|
|
1157
|
+
else if (this.#enableReconnect) this.#scheduleReconnect();
|
|
1158
|
+
}
|
|
1159
|
+
return handle;
|
|
1160
|
+
}
|
|
1161
|
+
#forgetLiveGroup(group, reason) {
|
|
1162
|
+
if (this.#liveByFp.get(group.fp) === group) this.#liveByFp.delete(group.fp);
|
|
1163
|
+
this.#liveBySubId.delete(group.sub.id);
|
|
1164
|
+
this.#subs.delete(group.sub.id);
|
|
1165
|
+
group.sub.end(reason);
|
|
1166
|
+
}
|
|
1167
|
+
#detachLive(group, handle) {
|
|
1168
|
+
if (!group.attachments.delete(handle)) return;
|
|
1169
|
+
if (group.attachments.size > 0) return;
|
|
1170
|
+
this.#endLiveGroup(group, {
|
|
1171
|
+
sendClose: true,
|
|
1172
|
+
reason: "closed by client"
|
|
708
1173
|
});
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
}
|
|
1174
|
+
}
|
|
1175
|
+
#endLiveGroup(group, opts) {
|
|
1176
|
+
const { id } = group.sub;
|
|
1177
|
+
const remaining = [...group.attachments];
|
|
1178
|
+
group.attachments.clear();
|
|
1179
|
+
this.#forgetLiveGroup(group, opts.reason);
|
|
1180
|
+
if (opts.sendClose) try {
|
|
1181
|
+
if (this.#isOpen()) this.#send(["CLOSE", id]);
|
|
1182
|
+
} catch {}
|
|
1183
|
+
for (const att of remaining) invokeSafely(() => {
|
|
1184
|
+
att.close(opts.reason);
|
|
1185
|
+
});
|
|
1186
|
+
}
|
|
1187
|
+
#deliverLiveEvent(group, event) {
|
|
1188
|
+
const { sub } = group;
|
|
1189
|
+
const attachments = [...group.attachments];
|
|
1190
|
+
for (const att of attachments) att.notifyReceived(event.id);
|
|
1191
|
+
if (sub.isAtWatermark(event.id)) return;
|
|
1192
|
+
const recipients = [];
|
|
1193
|
+
for (const att of attachments) {
|
|
1194
|
+
if (att.closed) continue;
|
|
1195
|
+
if (!att.alreadyHas(event.id)) recipients.push(att);
|
|
1196
|
+
}
|
|
1197
|
+
if (recipients.length === 0) return;
|
|
1198
|
+
if (!this.#acceptEvent(event)) return;
|
|
1199
|
+
sub.noteVerified(event);
|
|
1200
|
+
for (const att of recipients) att.deliver(event);
|
|
1201
|
+
}
|
|
1202
|
+
#deliverLiveEose(group) {
|
|
1203
|
+
const attachments = [...group.attachments];
|
|
1204
|
+
for (const att of attachments) att.markEose();
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
721
1207
|
//#endregion
|
|
722
1208
|
//#region src/relay/relay.ts
|
|
723
1209
|
/**
|
|
@@ -762,12 +1248,11 @@ var Relay = class Relay {
|
|
|
762
1248
|
#connectFinish;
|
|
763
1249
|
#connectTimer;
|
|
764
1250
|
#socketHandlers;
|
|
765
|
-
#
|
|
766
|
-
#
|
|
767
|
-
#
|
|
768
|
-
#
|
|
769
|
-
#
|
|
770
|
-
#counts = /* @__PURE__ */ new Map();
|
|
1251
|
+
#subscriptions;
|
|
1252
|
+
#ok = new OkTracker();
|
|
1253
|
+
#countRequests = new CountTracker();
|
|
1254
|
+
#authState;
|
|
1255
|
+
#ensureAuthed;
|
|
771
1256
|
#neg = /* @__PURE__ */ new Map();
|
|
772
1257
|
#WS;
|
|
773
1258
|
#verify;
|
|
@@ -777,13 +1262,6 @@ var Relay = class Relay {
|
|
|
777
1262
|
#enableReconnect;
|
|
778
1263
|
#backoff;
|
|
779
1264
|
#serial = 0;
|
|
780
|
-
#challenge;
|
|
781
|
-
#authedChallenge;
|
|
782
|
-
/** Challenge value already answered on this connection; duplicates are not re-signed. */
|
|
783
|
-
#answeredChallenge;
|
|
784
|
-
/** Settled OK verdict for `#answeredChallenge`; set only once the relay replies. */
|
|
785
|
-
#answeredResult;
|
|
786
|
-
#authPromise;
|
|
787
1265
|
#authSigner;
|
|
788
1266
|
/** `close()`/`disconnect()` was called; cleared by the next `connect()`. */
|
|
789
1267
|
#manualStop = false;
|
|
@@ -791,13 +1269,7 @@ var Relay = class Relay {
|
|
|
791
1269
|
#reconnectTimer;
|
|
792
1270
|
#enablePing;
|
|
793
1271
|
#ping;
|
|
794
|
-
#
|
|
795
|
-
notice: /* @__PURE__ */ new Set(),
|
|
796
|
-
close: /* @__PURE__ */ new Set(),
|
|
797
|
-
auth: /* @__PURE__ */ new Set(),
|
|
798
|
-
reconnect: /* @__PURE__ */ new Set(),
|
|
799
|
-
invalidevent: /* @__PURE__ */ new Set()
|
|
800
|
-
};
|
|
1272
|
+
#events = new Emitter();
|
|
801
1273
|
constructor(url, opts = {}) {
|
|
802
1274
|
this.url = normalizeURL(url);
|
|
803
1275
|
this.#WS = opts.websocketImplementation ?? getWebSocketImplementation();
|
|
@@ -821,21 +1293,29 @@ var Relay = class Relay {
|
|
|
821
1293
|
pingIntervalMs: opts.pingIntervalMs ?? 29e3,
|
|
822
1294
|
pingTimeoutMs: opts.pingTimeoutMs ?? 2e4
|
|
823
1295
|
});
|
|
824
|
-
this.#
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
subs: this.#subs,
|
|
828
|
-
connected: () => this.#isOpen(),
|
|
829
|
-
enableReconnect: () => this.#enableReconnect,
|
|
1296
|
+
this.#subscriptions = new SubscriptionRegistry({
|
|
1297
|
+
isOpen: () => this.#isOpen(),
|
|
1298
|
+
enableReconnect: this.#enableReconnect,
|
|
830
1299
|
send: (message) => this.#send(message),
|
|
831
1300
|
scheduleReconnect: () => this.#scheduleReconnect(),
|
|
832
|
-
acceptEvent: (event) => this.#acceptEvent(event)
|
|
833
|
-
|
|
834
|
-
|
|
1301
|
+
acceptEvent: (event) => this.#acceptEvent(event)
|
|
1302
|
+
});
|
|
1303
|
+
this.#authState = new RelayAuth({
|
|
1304
|
+
url: this.url,
|
|
1305
|
+
isOpen: () => this.#isOpen(),
|
|
1306
|
+
sendAuth: (event, timeoutMs) => this.#ok.track(event.id, {
|
|
1307
|
+
timeoutMs,
|
|
1308
|
+
timeoutError: () => new RelayTimeoutError("auth timed out", this.url),
|
|
1309
|
+
sendError: () => new RelayPublishError("auth send failed", this.url),
|
|
1310
|
+
send: () => this.#send(["AUTH", event])
|
|
1311
|
+
}),
|
|
1312
|
+
defaultTimeoutMs: this.#publishTimeoutMs
|
|
1313
|
+
});
|
|
1314
|
+
this.#ensureAuthed = this.#authState.ensureAuthed.bind(this.#authState, this.#authSigner);
|
|
835
1315
|
}
|
|
836
1316
|
/** Latest NIP-42 challenge, if any. */
|
|
837
1317
|
get challenge() {
|
|
838
|
-
return this.#challenge;
|
|
1318
|
+
return this.#authState.challenge;
|
|
839
1319
|
}
|
|
840
1320
|
get connected() {
|
|
841
1321
|
return this.#isOpen();
|
|
@@ -857,11 +1337,11 @@ var Relay = class Relay {
|
|
|
857
1337
|
}
|
|
858
1338
|
/** User subscriptions only; dummy ping REQs are not counted. */
|
|
859
1339
|
get subscriptionCount() {
|
|
860
|
-
return this.#
|
|
1340
|
+
return this.#subscriptions.size;
|
|
861
1341
|
}
|
|
862
1342
|
/** One-shot requests still awaiting a relay reply (EVENT ACK, COUNT, NEG). */
|
|
863
1343
|
get inFlightCount() {
|
|
864
|
-
return this.#
|
|
1344
|
+
return this.#ok.size + this.#countRequests.size + this.#neg.size;
|
|
865
1345
|
}
|
|
866
1346
|
/**
|
|
867
1347
|
* Listen for a relay event. Multiple listeners per type are allowed and fire in registration
|
|
@@ -869,14 +1349,7 @@ var Relay = class Relay {
|
|
|
869
1349
|
* function.
|
|
870
1350
|
*/
|
|
871
1351
|
on(type, listener) {
|
|
872
|
-
|
|
873
|
-
set.add(listener);
|
|
874
|
-
return () => {
|
|
875
|
-
set.delete(listener);
|
|
876
|
-
};
|
|
877
|
-
}
|
|
878
|
-
#emit(type, payload) {
|
|
879
|
-
for (const listener of [...this.#listeners[type]]) invokeSafely(() => listener(payload));
|
|
1352
|
+
return this.#events.on(type, listener);
|
|
880
1353
|
}
|
|
881
1354
|
static async connect(url, opts) {
|
|
882
1355
|
const relay = new Relay(url, opts);
|
|
@@ -912,6 +1385,15 @@ var Relay = class Relay {
|
|
|
912
1385
|
} catch {}
|
|
913
1386
|
if (this.#ws === ws) this.#ws = void 0;
|
|
914
1387
|
};
|
|
1388
|
+
const guarded = (fn) => {
|
|
1389
|
+
return () => {
|
|
1390
|
+
if (gen !== this.#gen) {
|
|
1391
|
+
release();
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
fn();
|
|
1395
|
+
};
|
|
1396
|
+
};
|
|
915
1397
|
const finish = (err) => {
|
|
916
1398
|
if (settled) return;
|
|
917
1399
|
settled = true;
|
|
@@ -924,18 +1406,14 @@ var Relay = class Relay {
|
|
|
924
1406
|
};
|
|
925
1407
|
this.#connecting = connecting;
|
|
926
1408
|
this.#connectFinish = finish;
|
|
927
|
-
const timer = setTimeout(() => {
|
|
928
|
-
if (gen !== this.#gen) {
|
|
929
|
-
release();
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
1409
|
+
const timer = setTimeout(guarded(() => {
|
|
932
1410
|
release();
|
|
933
1411
|
finish(new RelayTimeoutError("connection timed out", this.url));
|
|
934
1412
|
if (gen === this.#gen) this.#handleSocketDeath("connection timed out", {
|
|
935
1413
|
fromConnectAttempt: true,
|
|
936
1414
|
gen
|
|
937
1415
|
});
|
|
938
|
-
}, timeoutMs);
|
|
1416
|
+
}), timeoutMs);
|
|
939
1417
|
this.#connectTimer = timer;
|
|
940
1418
|
try {
|
|
941
1419
|
ws = new this.#WS(this.url);
|
|
@@ -949,36 +1427,24 @@ var Relay = class Relay {
|
|
|
949
1427
|
return;
|
|
950
1428
|
}
|
|
951
1429
|
this.#ws = ws;
|
|
952
|
-
const onOpen = () => {
|
|
953
|
-
if (gen !== this.#gen) {
|
|
954
|
-
release();
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
1430
|
+
const onOpen = guarded(() => {
|
|
957
1431
|
this.#setStatus(RelayStatus.Connected);
|
|
958
1432
|
const wasReconnect = this.#reconnectAttempts > 0;
|
|
959
|
-
this.#
|
|
960
|
-
this.#
|
|
961
|
-
this.#authedChallenge = void 0;
|
|
962
|
-
this.#answeredChallenge = void 0;
|
|
963
|
-
this.#answeredResult = void 0;
|
|
964
|
-
if (!resubscribeAll(this.#live)) {
|
|
1433
|
+
this.#authState.resetConnection();
|
|
1434
|
+
if (!this.#subscriptions.replayAll()) {
|
|
965
1435
|
this.#setStatus(RelayStatus.Disconnected);
|
|
966
1436
|
release();
|
|
967
1437
|
finish(new RelayConnectionError("connection failed", this.url));
|
|
968
|
-
if (this.#enableReconnect && !this.#manualStop && this.#
|
|
1438
|
+
if (this.#enableReconnect && !this.#manualStop && this.#subscriptions.size > 0) this.#scheduleReconnect();
|
|
969
1439
|
return;
|
|
970
1440
|
}
|
|
971
1441
|
this.#reconnectAttempts = 0;
|
|
972
1442
|
if (this.#enablePing) this.#ping.start();
|
|
973
1443
|
else this.#ping.stop();
|
|
974
|
-
if (wasReconnect) this.#emit("reconnect", void 0);
|
|
1444
|
+
if (wasReconnect) this.#events.emit("reconnect", void 0);
|
|
975
1445
|
finish();
|
|
976
|
-
};
|
|
977
|
-
const onError = () => {
|
|
978
|
-
if (gen !== this.#gen) {
|
|
979
|
-
release();
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
1446
|
+
});
|
|
1447
|
+
const onError = guarded(() => {
|
|
982
1448
|
const fromConnectAttempt = !settled;
|
|
983
1449
|
release();
|
|
984
1450
|
finish(new RelayConnectionError("connection failed", this.url));
|
|
@@ -986,12 +1452,8 @@ var Relay = class Relay {
|
|
|
986
1452
|
fromConnectAttempt,
|
|
987
1453
|
gen
|
|
988
1454
|
});
|
|
989
|
-
};
|
|
990
|
-
const onClose = () => {
|
|
991
|
-
if (gen !== this.#gen) {
|
|
992
|
-
release();
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
1455
|
+
});
|
|
1456
|
+
const onClose = guarded(() => {
|
|
995
1457
|
const fromConnectAttempt = !settled;
|
|
996
1458
|
release();
|
|
997
1459
|
if (fromConnectAttempt) finish(new RelayConnectionError("websocket closed", this.url));
|
|
@@ -999,7 +1461,7 @@ var Relay = class Relay {
|
|
|
999
1461
|
fromConnectAttempt,
|
|
1000
1462
|
gen
|
|
1001
1463
|
});
|
|
1002
|
-
};
|
|
1464
|
+
});
|
|
1003
1465
|
const onMessage = (ev) => {
|
|
1004
1466
|
if (gen !== this.#gen) return;
|
|
1005
1467
|
const data = typeof ev === "object" && ev !== null && "data" in ev ? ev.data : ev;
|
|
@@ -1046,12 +1508,12 @@ var Relay = class Relay {
|
|
|
1046
1508
|
this.#ping.stop();
|
|
1047
1509
|
this.#connectFinish?.(new RelayClosedError("relay closed", this.url));
|
|
1048
1510
|
try {
|
|
1049
|
-
|
|
1511
|
+
this.#subscriptions.closeAll("relay closed");
|
|
1050
1512
|
} finally {
|
|
1051
1513
|
this.#failPending(new RelayClosedError("relay closed", this.url));
|
|
1052
1514
|
this.#detachSocketHandlers();
|
|
1053
1515
|
this.#teardownSocket();
|
|
1054
|
-
this.#emit("close", void 0);
|
|
1516
|
+
this.#events.emit("close", void 0);
|
|
1055
1517
|
}
|
|
1056
1518
|
}
|
|
1057
1519
|
/**
|
|
@@ -1086,28 +1548,10 @@ var Relay = class Relay {
|
|
|
1086
1548
|
this.#reconnectTimer = void 0;
|
|
1087
1549
|
}
|
|
1088
1550
|
}
|
|
1089
|
-
#rejectPublishes(err) {
|
|
1090
|
-
for (const [, waiter] of this.#publishes) {
|
|
1091
|
-
clearTimeout(waiter.timer);
|
|
1092
|
-
waiter.reject(err);
|
|
1093
|
-
}
|
|
1094
|
-
this.#publishes.clear();
|
|
1095
|
-
}
|
|
1096
|
-
#rejectCounts(err) {
|
|
1097
|
-
for (const [, waiter] of this.#counts) {
|
|
1098
|
-
clearTimeout(waiter.timer);
|
|
1099
|
-
waiter.reject(err);
|
|
1100
|
-
}
|
|
1101
|
-
this.#counts.clear();
|
|
1102
|
-
}
|
|
1103
|
-
#rejectNeg(err) {
|
|
1104
|
-
for (const session of this.#neg.values()) failNegSession(session, err);
|
|
1105
|
-
this.#neg.clear();
|
|
1106
|
-
}
|
|
1107
1551
|
#failPending(err) {
|
|
1108
|
-
this.#
|
|
1109
|
-
this.#
|
|
1110
|
-
this.#
|
|
1552
|
+
this.#ok.rejectAll(err);
|
|
1553
|
+
this.#countRequests.rejectAll(err);
|
|
1554
|
+
failAllNegSessions(this.#neg, err);
|
|
1111
1555
|
}
|
|
1112
1556
|
/** Unexpected socket death. Keep subscriptions if reconnecting. */
|
|
1113
1557
|
#handleSocketDeath(reason, opts) {
|
|
@@ -1117,15 +1561,15 @@ var Relay = class Relay {
|
|
|
1117
1561
|
this.#detachSocketHandlers();
|
|
1118
1562
|
this.#ws = void 0;
|
|
1119
1563
|
this.#failPending(new RelayClosedError(reason, this.url));
|
|
1120
|
-
if (this.#enableReconnect && !this.#manualStop && this.#
|
|
1564
|
+
if (this.#enableReconnect && !this.#manualStop && this.#subscriptions.size > 0) {
|
|
1121
1565
|
this.#setStatus(RelayStatus.Disconnected);
|
|
1122
1566
|
this.#scheduleReconnect();
|
|
1123
1567
|
return;
|
|
1124
1568
|
}
|
|
1125
1569
|
this.#setStatus(RelayStatus.Disconnected);
|
|
1126
|
-
if (opts.fromConnectAttempt !== true || this.#
|
|
1127
|
-
|
|
1128
|
-
if (!this.#manualStop) this.#emit("close", void 0);
|
|
1570
|
+
if (opts.fromConnectAttempt !== true || this.#subscriptions.size > 0) {
|
|
1571
|
+
this.#subscriptions.closeAll(reason);
|
|
1572
|
+
if (!this.#manualStop) this.#events.emit("close", void 0);
|
|
1129
1573
|
}
|
|
1130
1574
|
this.#setStatus(RelayStatus.Closed);
|
|
1131
1575
|
}
|
|
@@ -1141,7 +1585,7 @@ var Relay = class Relay {
|
|
|
1141
1585
|
try {
|
|
1142
1586
|
await this.connect();
|
|
1143
1587
|
} catch {
|
|
1144
|
-
if (this.#enableReconnect && !this.#manualStop && this.#
|
|
1588
|
+
if (this.#enableReconnect && !this.#manualStop && this.#subscriptions.size > 0 && !this.#isOpen()) this.#scheduleReconnect();
|
|
1145
1589
|
}
|
|
1146
1590
|
})();
|
|
1147
1591
|
}, delay);
|
|
@@ -1162,91 +1606,78 @@ var Relay = class Relay {
|
|
|
1162
1606
|
case "EVENT": {
|
|
1163
1607
|
const [, subId, event] = msg;
|
|
1164
1608
|
if (this.#ping.hasWaiter(subId)) return;
|
|
1165
|
-
|
|
1609
|
+
this.#subscriptions.handleEvent(subId, event);
|
|
1166
1610
|
break;
|
|
1167
1611
|
}
|
|
1168
1612
|
case "EOSE": {
|
|
1169
1613
|
const [, subId] = msg;
|
|
1170
1614
|
if (this.#ping.finishDummyPing(subId)) return;
|
|
1171
|
-
|
|
1615
|
+
this.#subscriptions.handleEose(subId);
|
|
1172
1616
|
break;
|
|
1173
1617
|
}
|
|
1174
1618
|
case "CLOSED": {
|
|
1175
1619
|
const [, subId, reason] = msg;
|
|
1176
1620
|
if (this.#ping.finishDummyPing(subId)) return;
|
|
1177
|
-
const countWaiter = this.#
|
|
1621
|
+
const countWaiter = this.#countRequests.get(subId);
|
|
1178
1622
|
if (countWaiter) {
|
|
1179
1623
|
if (isAuthRequired(reason) && this.#authSigner && !countWaiter.authRetried) {
|
|
1180
1624
|
countWaiter.authRetried = true;
|
|
1181
|
-
this.#
|
|
1625
|
+
this.#countRequests.authRetry(countWaiter, {
|
|
1626
|
+
ensureAuthed: this.#ensureAuthed,
|
|
1627
|
+
isOpen: () => this.#isOpen(),
|
|
1628
|
+
failError: () => new RelayClosedError(reason || "COUNT closed", this.url),
|
|
1629
|
+
send: () => this.#send([
|
|
1630
|
+
"COUNT",
|
|
1631
|
+
subId,
|
|
1632
|
+
...countWaiter.filters
|
|
1633
|
+
])
|
|
1634
|
+
});
|
|
1182
1635
|
return;
|
|
1183
1636
|
}
|
|
1184
1637
|
countWaiter.reject(new RelayClosedError(reason || "COUNT closed", this.url));
|
|
1185
1638
|
return;
|
|
1186
1639
|
}
|
|
1187
|
-
const sub = this.#
|
|
1640
|
+
const sub = this.#subscriptions.get(subId);
|
|
1188
1641
|
if (!sub) return;
|
|
1189
|
-
if (isAuthRequired(reason) && this.#authSigner &&
|
|
1190
|
-
sub
|
|
1191
|
-
this.#authThenResubscribe(sub, reason);
|
|
1642
|
+
if (isAuthRequired(reason) && this.#authSigner && sub.beginAuthRetry()) {
|
|
1643
|
+
this.#subscriptions.authRetry(sub, reason, this.#ensureAuthed);
|
|
1192
1644
|
return;
|
|
1193
1645
|
}
|
|
1194
|
-
|
|
1646
|
+
this.#subscriptions.drop(sub, reason);
|
|
1195
1647
|
break;
|
|
1196
1648
|
}
|
|
1197
1649
|
case "OK": {
|
|
1198
1650
|
const [, eventId, ok, message] = msg;
|
|
1199
|
-
const waiter = this.#
|
|
1651
|
+
const waiter = this.#ok.handleOk(eventId, ok, message, (w) => !ok && isAuthRequired(message) && w.event !== void 0 && !w.authRetried && this.#authSigner !== void 0);
|
|
1200
1652
|
if (!waiter) return;
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
this.#
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
clearTimeout(waiter.timer);
|
|
1207
|
-
this.#publishes.delete(eventId);
|
|
1208
|
-
waiter.resolve({
|
|
1209
|
-
ok,
|
|
1210
|
-
message
|
|
1653
|
+
waiter.authRetried = true;
|
|
1654
|
+
this.#ok.authRetry(waiter, message, {
|
|
1655
|
+
ensureAuthed: this.#ensureAuthed,
|
|
1656
|
+
isOpen: () => this.#isOpen(),
|
|
1657
|
+
send: (event) => this.#send(["EVENT", event])
|
|
1211
1658
|
});
|
|
1212
|
-
|
|
1659
|
+
return;
|
|
1213
1660
|
}
|
|
1214
1661
|
case "COUNT": {
|
|
1215
1662
|
const [, countId, payload] = msg;
|
|
1216
|
-
const waiter = this.#
|
|
1663
|
+
const waiter = this.#countRequests.get(countId);
|
|
1217
1664
|
if (!waiter) return;
|
|
1218
1665
|
waiter.resolve(payload);
|
|
1219
1666
|
break;
|
|
1220
1667
|
}
|
|
1221
|
-
case "NEG-MSG":
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
if (!session) return;
|
|
1225
|
-
pushNegMsg(session, hex);
|
|
1226
|
-
break;
|
|
1227
|
-
}
|
|
1228
|
-
case "NEG-ERR": {
|
|
1229
|
-
const [, negId, reason] = msg;
|
|
1230
|
-
const session = this.#neg.get(negId);
|
|
1231
|
-
if (!session) return;
|
|
1232
|
-
failNegErr(session, reason);
|
|
1668
|
+
case "NEG-MSG":
|
|
1669
|
+
case "NEG-ERR":
|
|
1670
|
+
dispatchNegMessage(this.#neg, msg);
|
|
1233
1671
|
break;
|
|
1234
|
-
}
|
|
1235
1672
|
case "NOTICE": {
|
|
1236
1673
|
const [, notice] = msg;
|
|
1237
|
-
this.#emit("notice", notice);
|
|
1674
|
+
this.#events.emit("notice", notice);
|
|
1238
1675
|
break;
|
|
1239
1676
|
}
|
|
1240
1677
|
case "AUTH": {
|
|
1241
1678
|
const [, authChallenge] = msg;
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
this.#authedChallenge = void 0;
|
|
1245
|
-
this.#answeredChallenge = void 0;
|
|
1246
|
-
this.#answeredResult = void 0;
|
|
1247
|
-
}
|
|
1248
|
-
this.#challenge = authChallenge;
|
|
1249
|
-
this.#emit("auth", authChallenge);
|
|
1679
|
+
this.#authState.handleChallenge(authChallenge);
|
|
1680
|
+
this.#events.emit("auth", authChallenge);
|
|
1250
1681
|
break;
|
|
1251
1682
|
}
|
|
1252
1683
|
}
|
|
@@ -1256,19 +1687,18 @@ var Relay = class Relay {
|
|
|
1256
1687
|
if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
|
|
1257
1688
|
if (!this.#isOpen() && !this.#enableReconnect) throw new RelayClosedError("not connected", this.url);
|
|
1258
1689
|
const canonical = canonicalizeFilters(filters);
|
|
1259
|
-
|
|
1260
|
-
return subscribeLive(this.#live, canonical, opts);
|
|
1690
|
+
return this.#subscriptions.open(canonical, opts);
|
|
1261
1691
|
}
|
|
1262
1692
|
#acceptEvent(event) {
|
|
1263
1693
|
if (this.#verifyDead) return false;
|
|
1264
1694
|
try {
|
|
1265
1695
|
const ok = this.#verify(event);
|
|
1266
|
-
if (!ok) this.#emit("invalidevent", void 0);
|
|
1696
|
+
if (!ok) this.#events.emit("invalidevent", void 0);
|
|
1267
1697
|
return ok;
|
|
1268
1698
|
} catch (error) {
|
|
1269
1699
|
if (error instanceof WasmPoisonedError) {
|
|
1270
1700
|
this.#verifyDead = true;
|
|
1271
|
-
this.#emit("notice", "wasm-poisoned: instance aborted");
|
|
1701
|
+
this.#events.emit("notice", "wasm-poisoned: instance aborted");
|
|
1272
1702
|
return false;
|
|
1273
1703
|
}
|
|
1274
1704
|
throw error;
|
|
@@ -1299,35 +1729,16 @@ var Relay = class Relay {
|
|
|
1299
1729
|
*/
|
|
1300
1730
|
async publish(event, opts) {
|
|
1301
1731
|
if (!this.#isOpen()) throw new RelayClosedError("not connected", this.url);
|
|
1302
|
-
const existing = this.#
|
|
1303
|
-
if (existing
|
|
1732
|
+
const existing = this.#ok.inFlight(event.id);
|
|
1733
|
+
if (existing !== void 0) return existing;
|
|
1304
1734
|
const timeoutMs = opts?.timeoutMs ?? this.#publishTimeoutMs;
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
this.#publishes.delete(event.id);
|
|
1312
|
-
reject(new RelayTimeoutError("publish timed out", this.url));
|
|
1313
|
-
}
|
|
1314
|
-
}, timeoutMs),
|
|
1315
|
-
event,
|
|
1316
|
-
timeoutMs,
|
|
1317
|
-
get promise() {
|
|
1318
|
-
return promise;
|
|
1319
|
-
}
|
|
1320
|
-
};
|
|
1321
|
-
this.#publishes.set(event.id, waiter);
|
|
1322
|
-
try {
|
|
1323
|
-
this.#send(["EVENT", event]);
|
|
1324
|
-
} catch (error) {
|
|
1325
|
-
clearTimeout(waiter.timer);
|
|
1326
|
-
if (this.#publishes.get(event.id) === waiter) this.#publishes.delete(event.id);
|
|
1327
|
-
reject(error instanceof Error ? error : new RelayPublishError("publish failed", this.url));
|
|
1328
|
-
}
|
|
1735
|
+
return this.#ok.track(event.id, {
|
|
1736
|
+
timeoutMs,
|
|
1737
|
+
timeoutError: () => new RelayTimeoutError("publish timed out", this.url),
|
|
1738
|
+
sendError: () => new RelayPublishError("publish failed", this.url),
|
|
1739
|
+
event,
|
|
1740
|
+
send: () => this.#send(["EVENT", event])
|
|
1329
1741
|
});
|
|
1330
|
-
return promise;
|
|
1331
1742
|
}
|
|
1332
1743
|
/**
|
|
1333
1744
|
* NIP-45 COUNT: ask the relay how many events match `filters`. Resolves with the COUNT payload
|
|
@@ -1340,105 +1751,22 @@ var Relay = class Relay {
|
|
|
1340
1751
|
const canonical = canonicalizeFilters(filters);
|
|
1341
1752
|
const id = opts?.id === void 0 ? this.nextSubId("count") : createSubscriptionId(opts.id);
|
|
1342
1753
|
const timeoutMs = opts?.timeoutMs ?? this.#publishTimeoutMs;
|
|
1343
|
-
return
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
waiter.timer = void 0;
|
|
1355
|
-
this.#counts.delete(id);
|
|
1356
|
-
cleanup();
|
|
1357
|
-
reject(err);
|
|
1358
|
-
},
|
|
1359
|
-
timer: void 0,
|
|
1360
|
-
filters: [...canonical],
|
|
1361
|
-
authRetried: false,
|
|
1362
|
-
timeoutMs
|
|
1363
|
-
};
|
|
1364
|
-
const cleanup = onAbort(opts?.signal, () => {
|
|
1365
|
-
if (opts?.signal) waiter.reject(abortReason(opts.signal));
|
|
1366
|
-
});
|
|
1367
|
-
waiter.timer = setTimeout(() => {
|
|
1368
|
-
waiter.reject(new RelayTimeoutError("count timed out", this.url));
|
|
1369
|
-
}, timeoutMs);
|
|
1370
|
-
this.#counts.set(id, waiter);
|
|
1371
|
-
try {
|
|
1372
|
-
this.#send([
|
|
1373
|
-
"COUNT",
|
|
1374
|
-
id,
|
|
1375
|
-
...canonical
|
|
1376
|
-
]);
|
|
1377
|
-
} catch (error) {
|
|
1378
|
-
waiter.reject(error instanceof Error ? error : new RelayPublishError("count failed", this.url));
|
|
1379
|
-
}
|
|
1754
|
+
return this.#countRequests.track(id, {
|
|
1755
|
+
timeoutMs,
|
|
1756
|
+
timeoutError: () => new RelayTimeoutError("count timed out", this.url),
|
|
1757
|
+
sendError: () => new RelayPublishError("count failed", this.url),
|
|
1758
|
+
filters: [...canonical],
|
|
1759
|
+
signal: opts?.signal,
|
|
1760
|
+
send: () => this.#send([
|
|
1761
|
+
"COUNT",
|
|
1762
|
+
id,
|
|
1763
|
+
...canonical
|
|
1764
|
+
])
|
|
1380
1765
|
});
|
|
1381
1766
|
}
|
|
1382
1767
|
/** NIP-42 AUTH: sign the current challenge and wait for OK. */
|
|
1383
1768
|
async auth(sign, opts) {
|
|
1384
|
-
|
|
1385
|
-
if (challenge === void 0 || challenge === "") throw new RelayError("no AUTH challenge received from relay", this.url);
|
|
1386
|
-
if (this.#authPromise) return this.#authPromise;
|
|
1387
|
-
if (this.#answeredChallenge === challenge && this.#answeredResult !== void 0) return this.#answeredResult;
|
|
1388
|
-
const pending = (async () => {
|
|
1389
|
-
const template = makeAuthEvent(this.url, challenge);
|
|
1390
|
-
let event;
|
|
1391
|
-
try {
|
|
1392
|
-
event = await sign(template);
|
|
1393
|
-
} catch (error) {
|
|
1394
|
-
if (error instanceof NoSignerError) return {
|
|
1395
|
-
ok: false,
|
|
1396
|
-
message: "auth: no signer"
|
|
1397
|
-
};
|
|
1398
|
-
throw error;
|
|
1399
|
-
}
|
|
1400
|
-
if (!this.#isOpen()) throw new RelayClosedError("not connected", this.url);
|
|
1401
|
-
const timeoutMs = opts?.timeoutMs ?? this.#publishTimeoutMs;
|
|
1402
|
-
const authPromise = new Promise((resolve, reject) => {
|
|
1403
|
-
const waiter = {
|
|
1404
|
-
resolve: (result) => {
|
|
1405
|
-
if (this.#challenge === challenge) {
|
|
1406
|
-
this.#answeredChallenge = challenge;
|
|
1407
|
-
this.#answeredResult = result;
|
|
1408
|
-
}
|
|
1409
|
-
resolve(result);
|
|
1410
|
-
},
|
|
1411
|
-
reject,
|
|
1412
|
-
timer: setTimeout(() => {
|
|
1413
|
-
if (this.#publishes.get(event.id) === waiter) {
|
|
1414
|
-
this.#publishes.delete(event.id);
|
|
1415
|
-
reject(new RelayTimeoutError("auth timed out", this.url));
|
|
1416
|
-
}
|
|
1417
|
-
}, timeoutMs),
|
|
1418
|
-
timeoutMs,
|
|
1419
|
-
get promise() {
|
|
1420
|
-
return authPromise;
|
|
1421
|
-
}
|
|
1422
|
-
};
|
|
1423
|
-
this.#publishes.set(event.id, waiter);
|
|
1424
|
-
try {
|
|
1425
|
-
this.#send(["AUTH", event]);
|
|
1426
|
-
} catch (error) {
|
|
1427
|
-
clearTimeout(waiter.timer);
|
|
1428
|
-
if (this.#publishes.get(event.id) === waiter) this.#publishes.delete(event.id);
|
|
1429
|
-
reject(error instanceof Error ? error : new RelayPublishError("auth send failed", this.url));
|
|
1430
|
-
}
|
|
1431
|
-
});
|
|
1432
|
-
return authPromise;
|
|
1433
|
-
})();
|
|
1434
|
-
this.#authPromise = pending;
|
|
1435
|
-
try {
|
|
1436
|
-
const result = await pending;
|
|
1437
|
-
if (result.ok && this.#challenge === challenge) this.#authedChallenge = challenge;
|
|
1438
|
-
return result;
|
|
1439
|
-
} finally {
|
|
1440
|
-
if (this.#authPromise === pending) this.#authPromise = void 0;
|
|
1441
|
-
}
|
|
1769
|
+
return this.#authState.auth(sign, opts);
|
|
1442
1770
|
}
|
|
1443
1771
|
/**
|
|
1444
1772
|
* Clear a cached AUTH rejection for the current challenge so a later `auth()` signs again. A
|
|
@@ -1446,133 +1774,8 @@ var Relay = class Relay {
|
|
|
1446
1774
|
* `auth` event is re-fired so the pool's automatic auth can run once more.
|
|
1447
1775
|
*/
|
|
1448
1776
|
resetAuth() {
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
this.#answeredResult = void 0;
|
|
1452
|
-
}
|
|
1453
|
-
if (this.#challenge !== void 0 && this.#challenge !== this.#authedChallenge) {
|
|
1454
|
-
const challenge = this.#challenge;
|
|
1455
|
-
this.#emit("auth", challenge);
|
|
1456
|
-
}
|
|
1457
|
-
}
|
|
1458
|
-
async #ensureAuthed() {
|
|
1459
|
-
const signer = this.#authSigner;
|
|
1460
|
-
if (!signer) return false;
|
|
1461
|
-
for (let i = 0; i < 3; i++) {
|
|
1462
|
-
if (this.#challenge === void 0 || this.#challenge === "") return false;
|
|
1463
|
-
if (this.#authedChallenge === this.#challenge) return true;
|
|
1464
|
-
const signed = this.#challenge;
|
|
1465
|
-
const result = await this.auth(signer);
|
|
1466
|
-
if (this.#authedChallenge === this.#challenge) return true;
|
|
1467
|
-
if (!result.ok) {
|
|
1468
|
-
if (this.#challenge === void 0 || this.#challenge === "" || this.#challenge === signed) return false;
|
|
1469
|
-
continue;
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
return this.#authedChallenge === this.#challenge;
|
|
1473
|
-
}
|
|
1474
|
-
async #authThenResubscribe(sub, reason) {
|
|
1475
|
-
try {
|
|
1476
|
-
if (!await this.#ensureAuthed()) {
|
|
1477
|
-
dropSubscription(this.#live, sub, reason);
|
|
1478
|
-
return;
|
|
1479
|
-
}
|
|
1480
|
-
if (sub.closed || !this.#isOpen()) {
|
|
1481
|
-
dropSubscription(this.#live, sub, reason);
|
|
1482
|
-
return;
|
|
1483
|
-
}
|
|
1484
|
-
sub.eosed = false;
|
|
1485
|
-
const group = this.#liveBySubId.get(sub.id);
|
|
1486
|
-
if (group) for (const att of group.attachments) att.eosed = false;
|
|
1487
|
-
this.#send([
|
|
1488
|
-
"REQ",
|
|
1489
|
-
sub.id,
|
|
1490
|
-
...sub.replayFilters()
|
|
1491
|
-
]);
|
|
1492
|
-
} catch {
|
|
1493
|
-
dropSubscription(this.#live, sub, reason);
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
async #authThenRecount(id, waiter, reason) {
|
|
1497
|
-
if (waiter.timer !== void 0) {
|
|
1498
|
-
clearTimeout(waiter.timer);
|
|
1499
|
-
waiter.timer = void 0;
|
|
1500
|
-
}
|
|
1501
|
-
const fail = () => {
|
|
1502
|
-
waiter.reject(new RelayClosedError(reason || "COUNT closed", this.url));
|
|
1503
|
-
};
|
|
1504
|
-
try {
|
|
1505
|
-
const ok = await this.#ensureAuthed();
|
|
1506
|
-
if (this.#counts.get(id) !== waiter) return;
|
|
1507
|
-
if (!ok) {
|
|
1508
|
-
fail();
|
|
1509
|
-
return;
|
|
1510
|
-
}
|
|
1511
|
-
if (!this.#isOpen()) {
|
|
1512
|
-
fail();
|
|
1513
|
-
return;
|
|
1514
|
-
}
|
|
1515
|
-
waiter.timer = setTimeout(() => {
|
|
1516
|
-
waiter.reject(new RelayTimeoutError("count timed out", this.url));
|
|
1517
|
-
}, waiter.timeoutMs);
|
|
1518
|
-
this.#send([
|
|
1519
|
-
"COUNT",
|
|
1520
|
-
id,
|
|
1521
|
-
...waiter.filters
|
|
1522
|
-
]);
|
|
1523
|
-
} catch (error) {
|
|
1524
|
-
if (!this.#counts.has(id)) return;
|
|
1525
|
-
waiter.reject(error instanceof Error ? error : new RelayClosedError(reason || "COUNT closed", this.url));
|
|
1526
|
-
}
|
|
1527
|
-
}
|
|
1528
|
-
async #authThenRepublish(waiter, eventId, message) {
|
|
1529
|
-
if (waiter.timer !== void 0) {
|
|
1530
|
-
clearTimeout(waiter.timer);
|
|
1531
|
-
waiter.timer = void 0;
|
|
1532
|
-
}
|
|
1533
|
-
const finish = (result) => {
|
|
1534
|
-
if (waiter.timer !== void 0) clearTimeout(waiter.timer);
|
|
1535
|
-
if (this.#publishes.get(eventId) === waiter) this.#publishes.delete(eventId);
|
|
1536
|
-
waiter.resolve(result);
|
|
1537
|
-
};
|
|
1538
|
-
try {
|
|
1539
|
-
if (!waiter.event) {
|
|
1540
|
-
finish({
|
|
1541
|
-
ok: false,
|
|
1542
|
-
message
|
|
1543
|
-
});
|
|
1544
|
-
return;
|
|
1545
|
-
}
|
|
1546
|
-
const ok = await this.#ensureAuthed();
|
|
1547
|
-
if (this.#publishes.get(eventId) !== waiter) return;
|
|
1548
|
-
if (!ok) {
|
|
1549
|
-
finish({
|
|
1550
|
-
ok: false,
|
|
1551
|
-
message
|
|
1552
|
-
});
|
|
1553
|
-
return;
|
|
1554
|
-
}
|
|
1555
|
-
if (!this.#isOpen()) {
|
|
1556
|
-
finish({
|
|
1557
|
-
ok: false,
|
|
1558
|
-
message
|
|
1559
|
-
});
|
|
1560
|
-
return;
|
|
1561
|
-
}
|
|
1562
|
-
waiter.timer = setTimeout(() => {
|
|
1563
|
-
if (this.#publishes.get(eventId) === waiter) {
|
|
1564
|
-
this.#publishes.delete(eventId);
|
|
1565
|
-
waiter.reject(new RelayTimeoutError("publish timed out", this.url));
|
|
1566
|
-
}
|
|
1567
|
-
}, waiter.timeoutMs);
|
|
1568
|
-
this.#send(["EVENT", waiter.event]);
|
|
1569
|
-
} catch {
|
|
1570
|
-
if (!this.#publishes.has(eventId)) return;
|
|
1571
|
-
finish({
|
|
1572
|
-
ok: false,
|
|
1573
|
-
message
|
|
1574
|
-
});
|
|
1575
|
-
}
|
|
1777
|
+
const challenge = this.#authState.resetRejection();
|
|
1778
|
+
if (challenge !== void 0) this.#events.emit("auth", challenge);
|
|
1576
1779
|
}
|
|
1577
1780
|
/**
|
|
1578
1781
|
* NIP-77: run Negentropy reconciliation against this relay. Returns the local-only (`have`) and
|
|
@@ -1584,32 +1787,17 @@ var Relay = class Relay {
|
|
|
1584
1787
|
async negReconcile(filter, storage, opts) {
|
|
1585
1788
|
if (!this.#isOpen()) throw new RelayClosedError("not connected", this.url);
|
|
1586
1789
|
throwIfAborted(opts?.signal);
|
|
1587
|
-
const canonical = canonicalizeFilter(filter);
|
|
1588
1790
|
const id = opts?.id === void 0 ? this.nextSubId("neg") : assertSubscriptionId(opts.id);
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
id,
|
|
1600
|
-
timeoutMs,
|
|
1601
|
-
signal: opts?.signal,
|
|
1602
|
-
send: (message) => this.#send(message),
|
|
1603
|
-
url: this.url
|
|
1604
|
-
});
|
|
1605
|
-
} finally {
|
|
1606
|
-
if (this.#neg.get(id) === session) {
|
|
1607
|
-
this.#neg.delete(id);
|
|
1608
|
-
try {
|
|
1609
|
-
this.#send(["NEG-CLOSE", id]);
|
|
1610
|
-
} catch {}
|
|
1611
|
-
}
|
|
1612
|
-
}
|
|
1791
|
+
return runTrackedNegSession({
|
|
1792
|
+
sessions: this.#neg,
|
|
1793
|
+
id,
|
|
1794
|
+
storage,
|
|
1795
|
+
filter: canonicalizeFilter(filter),
|
|
1796
|
+
timeoutMs: opts?.timeoutMs ?? this.#publishTimeoutMs,
|
|
1797
|
+
signal: opts?.signal,
|
|
1798
|
+
send: (message) => this.#send(message),
|
|
1799
|
+
url: this.url
|
|
1800
|
+
});
|
|
1613
1801
|
}
|
|
1614
1802
|
/** Generate a unique subscription id for this relay instance. */
|
|
1615
1803
|
nextSubId(prefix = "sub") {
|
|
@@ -1620,4 +1808,4 @@ var Relay = class Relay {
|
|
|
1620
1808
|
//#endregion
|
|
1621
1809
|
export { getWebSocketImplementation as a, RelayConnectionError as c, RelaySuspendedError as d, RelayTimeoutError as f, subscriptionToAsyncIterable as i, RelayError as l, Relay as n, useWebSocketImplementation as o, RelayStatus as r, RelayClosedError as s, DEFAULT_REQUEST_TIMEOUT_MS as t, RelayPublishError as u };
|
|
1622
1810
|
|
|
1623
|
-
//# sourceMappingURL=relay-
|
|
1811
|
+
//# sourceMappingURL=relay-quCs7MHD.mjs.map
|