@qntx/nostr 0.7.0 → 0.9.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/README.md +1 -1
- package/dist/{builder-CdLE4J6O.mjs → builder-CVRmUeNm.mjs} +5 -5
- package/dist/builder-CVRmUeNm.mjs.map +1 -0
- package/dist/{builder-DgkqDylK.d.mts → builder-GBddxj8i.d.mts} +12 -12
- package/dist/{client-CILhdfsF.mjs → client-CeyDLoyO.mjs} +110 -90
- package/dist/client-CeyDLoyO.mjs.map +1 -0
- package/dist/client.d.mts +1 -1
- package/dist/client.mjs +1 -1
- package/dist/core.d.mts +8 -8
- package/dist/core.mjs +9 -9
- 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-C2eLiPpV.mjs} +16 -6
- package/dist/event-C2eLiPpV.mjs.map +1 -0
- package/dist/{event-DJeb_DIL.d.mts → event-j3BwI5dF.d.mts} +2 -4
- package/dist/{filter-jI_9htF7.d.mts → filter-CfEgQBXt.d.mts} +3 -5
- package/dist/{filter-BQl3GlHt.mjs → filter-TrF2LEoy.mjs} +3 -36
- package/dist/filter-TrF2LEoy.mjs.map +1 -0
- package/dist/{gossip-CcTp9VpU.mjs → gossip-5ezg0Quo.mjs} +4 -4
- package/dist/gossip-5ezg0Quo.mjs.map +1 -0
- package/dist/{gossip-C7HLW5kI.d.mts → gossip-BWb9HmfK.d.mts} +10 -7
- package/dist/gossip.d.mts +1 -1
- package/dist/gossip.mjs +1 -1
- package/dist/http-CuazNRIP.mjs.map +1 -1
- package/dist/{http-C4HwIu4B.d.mts → http-kwWG-V1X.d.mts} +9 -3
- package/dist/{index-Tx15HGAD.d.mts → index-C0mFpelb.d.mts} +9 -9
- package/dist/{index-DocgD9K0.d.mts → index-C1m5cHpd.d.mts} +42 -28
- package/dist/{index-InQZpHyQ.d.mts → index-CoX7SnO7.d.mts} +7 -4
- package/dist/{index-iLT8UzY3.d.mts → index-D0M3kjY2.d.mts} +23 -23
- package/dist/{index-DU9tRFTZ.d.mts → index-Hp26bgYG.d.mts} +11 -4
- package/dist/{index-DiXwNn2M.d.mts → index-JD1O7m2v.d.mts} +4 -4
- package/dist/index.d.mts +20 -20
- package/dist/index.mjs +20 -20
- package/dist/{instance-BcKzxHIP.mjs → instance-CI_IaZpf.mjs} +3 -3
- package/dist/{instance-BcKzxHIP.mjs.map → instance-CI_IaZpf.mjs.map} +1 -1
- package/dist/{instance-DjsL8h84.d.mts → instance-X_WkCnpR.d.mts} +3 -3
- package/dist/{key-D_5OgGT7.d.mts → key-CyKN1RTi.d.mts} +2 -2
- package/dist/{key-bshqfmZL.mjs → key-DY_HEIpY.mjs} +5 -4
- package/dist/key-DY_HEIpY.mjs.map +1 -0
- package/dist/{kind-Cxwhhkx8.mjs → kind-D8fENiiL.mjs} +11 -5
- package/dist/kind-D8fENiiL.mjs.map +1 -0
- package/dist/{loaders-DD0XAGzw.mjs → loaders-DifimjJ7.mjs} +11 -10
- package/dist/loaders-DifimjJ7.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-DENGQ_f6.mjs} +2 -2
- package/dist/memory-DENGQ_f6.mjs.map +1 -0
- package/dist/{memory-index-C6rujtoc.mjs → memory-index-D_lvlYAy.mjs} +4 -4
- package/dist/{memory-index-C6rujtoc.mjs.map → memory-index-D_lvlYAy.mjs.map} +1 -1
- package/dist/{message-CbBZOhq3.d.mts → message-CcUz22qh.d.mts} +6 -6
- package/dist/{message-CWLcFTQ1.mjs → message-Rd5Z_17h.mjs} +6 -5
- package/dist/message-Rd5Z_17h.mjs.map +1 -0
- package/dist/{nip17-cTDFfFVt.d.mts → nip17-B7MI82Iq.d.mts} +5 -5
- 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-O_vhOiK2.d.mts} +11 -11
- package/dist/{nip59-BFIihLYY.d.mts → nip59-CdDoJptA.d.mts} +10 -3
- package/dist/{nip65-52HiwiyD.d.mts → nip65-BL79HI8N.d.mts} +3 -3
- package/dist/{nip77-C9QcZn7b.d.mts → nip77-wHA_nEV5.d.mts} +7 -7
- package/dist/nips/blossom.d.mts +8 -8
- package/dist/nips/blossom.mjs +3 -3
- package/dist/nips/blossom.mjs.map +1 -1
- package/dist/nips/nip04.mjs +1 -1
- package/dist/nips/nip05.d.mts +9 -9
- package/dist/nips/nip05.mjs +1 -1
- package/dist/nips/nip05.mjs.map +1 -1
- package/dist/nips/nip10.d.mts +8 -8
- package/dist/nips/nip10.mjs +35 -33
- package/dist/nips/nip10.mjs.map +1 -1
- package/dist/nips/nip11.d.mts +28 -28
- package/dist/nips/nip11.mjs +1 -1
- package/dist/nips/nip11.mjs.map +1 -1
- package/dist/nips/nip13.d.mts +1 -1
- package/dist/nips/nip13.mjs +1 -1
- package/dist/nips/nip17.d.mts +1 -1
- package/dist/nips/nip17.mjs +7 -5
- 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 +20 -20
- package/dist/nips/nip27.mjs +2 -2
- package/dist/nips/nip27.mjs.map +1 -1
- package/dist/nips/nip42.d.mts +1 -1
- package/dist/nips/nip42.mjs +2 -2
- 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 +4 -4
- package/dist/nips/nip51.mjs.map +1 -1
- package/dist/nips/nip57.d.mts +19 -19
- package/dist/nips/nip57.mjs +4 -4
- package/dist/nips/nip57.mjs.map +1 -1
- package/dist/nips/nip59.d.mts +1 -1
- package/dist/nips/nip59.mjs +16 -8
- package/dist/nips/nip59.mjs.map +1 -1
- package/dist/nips/nip65.d.mts +1 -1
- package/dist/nips/nip65.mjs +3 -3
- package/dist/nips/nip77.d.mts +1 -1
- package/dist/nips/nip77.mjs +9 -6
- package/dist/nips/nip77.mjs.map +1 -1
- package/dist/nips/nip98.d.mts +1 -1
- package/dist/nips/nip98.mjs +4 -4
- package/dist/{pool-SzJKPIa3.d.mts → pool-BDI_u03Q.d.mts} +48 -28
- package/dist/{pool-YHRZgcQ3.mjs → pool-jok2-iBJ.mjs} +64 -47
- package/dist/pool-jok2-iBJ.mjs.map +1 -0
- package/dist/{put-DS0yzVJx.mjs → put-JsiCKMyZ.mjs} +5 -5
- package/dist/put-JsiCKMyZ.mjs.map +1 -0
- package/dist/{reactive-BJRoVHxU.mjs → reactive-6Wn9qf_d.mjs} +51 -29
- package/dist/reactive-6Wn9qf_d.mjs.map +1 -0
- package/dist/{reactive-BJ1ggn4d.d.mts → reactive-X4p6nABy.d.mts} +18 -9
- package/dist/{relay-CvxPiQ_m.mjs → relay-__QtO33h.mjs} +1059 -863
- package/dist/relay-__QtO33h.mjs.map +1 -0
- package/dist/relay.d.mts +4 -4
- package/dist/relay.mjs +2 -2
- package/dist/{signer-yW3slDC2.mjs → signer-D7PtVvQn.mjs} +16 -9
- package/dist/signer-D7PtVvQn.mjs.map +1 -0
- package/dist/signer.d.mts +2 -2
- package/dist/signer.mjs +2 -2
- package/dist/{storage-CZ52ifcQ.mjs → storage-oihjE0_x.mjs} +23 -21
- package/dist/storage-oihjE0_x.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 +4 -4
- package/dist/testing.mjs +8 -8
- package/dist/testing.mjs.map +1 -1
- package/dist/{types-DNmiscfd.d.mts → types-BJ4wZ7JJ.d.mts} +2 -2
- package/dist/{types-Cm-XMgtH.d.mts → types-Ccox7-5v.d.mts} +7 -7
- package/dist/{util-yFYzlbrm.mjs → util-DMF9AIO0.mjs} +6 -2
- package/dist/util-DMF9AIO0.mjs.map +1 -0
- package/dist/{verifier-CgTFpgsC.d.mts → verifier-BjL2M0ka.d.mts} +2 -2
- package/dist/{verifier-BTLOpo1n.mjs → verifier-uC_jd-Nw.mjs} +4 -4
- package/dist/verifier-uC_jd-Nw.mjs.map +1 -0
- package/dist/wasm.browser.d.mts +1 -1
- package/dist/wasm.browser.mjs +2 -2
- package/dist/wasm.d.mts +1 -1
- package/dist/wasm.mjs +2 -2
- package/package.json +5 -5
- package/dist/builder-CdLE4J6O.mjs.map +0 -1
- package/dist/client-CILhdfsF.mjs.map +0 -1
- package/dist/event-DinQMmJ1.mjs.map +0 -1
- package/dist/filter-BQl3GlHt.mjs.map +0 -1
- package/dist/gossip-CcTp9VpU.mjs.map +0 -1
- package/dist/key-bshqfmZL.mjs.map +0 -1
- package/dist/kind-Cxwhhkx8.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
- package/dist/verifier-BTLOpo1n.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-
|
|
3
|
-
import { n as canonicalizeFilters, r as filterFingerprint, t as canonicalizeFilter } from "./filter-
|
|
4
|
-
import { n as createSubscriptionId, r as encodeClientMessage, s as parseRelayMessage, t as assertSubscriptionId } from "./message-
|
|
5
|
-
import { t as NoSignerError } from "./error-
|
|
1
|
+
import { _ as MessageError, b as WasmPoisonedError, d as nowSeconds, u as normalizeURL, v as NostrError } from "./util-DMF9AIO0.mjs";
|
|
2
|
+
import { n as verifyEvent } from "./verifier-uC_jd-Nw.mjs";
|
|
3
|
+
import { n as canonicalizeFilters, o as matchFilters, r as filterFingerprint, t as canonicalizeFilter } from "./filter-TrF2LEoy.mjs";
|
|
4
|
+
import { n as createSubscriptionId, r as encodeClientMessage, s as parseRelayMessage, t as assertSubscriptionId } from "./message-Rd5Z_17h.mjs";
|
|
5
|
+
import { t as NoSignerError } from "./error-DlhGW4mJ.mjs";
|
|
6
|
+
import { i as throwIfAborted, n as onAbort, r as raceSignal, t as abortReason } from "./abort-DBq2STPj.mjs";
|
|
6
7
|
import { t as invokeSafely } from "./report-CHld4uWj.mjs";
|
|
7
8
|
import { isAuthRequired, makeAuthEvent } from "./nips/nip42.mjs";
|
|
8
|
-
import {
|
|
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,519 @@ 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
|
+
/**
|
|
398
|
+
* Advance the reconnect watermark after a verified EVENT. A far-future `created_at` (buggy or
|
|
399
|
+
* hostile publishers do exist on the public network) must not push the replayed `since` past wall
|
|
400
|
+
* clock — clamp it to the current wall clock. The worst case is re-delivery of events already
|
|
401
|
+
* seen, never event loss.
|
|
402
|
+
*/
|
|
403
|
+
noteVerified(event) {
|
|
404
|
+
const ts = Math.min(event.created_at, nowSeconds());
|
|
405
|
+
if (this.#lastCreatedAt === void 0 || ts > this.#lastCreatedAt) {
|
|
406
|
+
this.#lastCreatedAt = ts;
|
|
407
|
+
this.#idsAtWatermark.clear();
|
|
408
|
+
this.#idsAtWatermark.add(event.id);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
if (ts === this.#lastCreatedAt) this.#idsAtWatermark.add(event.id);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Filters for re-REQ. Original `filters` stay unchanged. NIP-01 `since` is inclusive — never
|
|
415
|
+
* `lastCreatedAt + 1`.
|
|
416
|
+
*/
|
|
417
|
+
replayFilters() {
|
|
418
|
+
const since = this.#lastCreatedAt;
|
|
419
|
+
if (since === void 0) return [...this.filters];
|
|
420
|
+
return this.filters.map((f) => ({
|
|
421
|
+
...f,
|
|
422
|
+
since: f.since === void 0 ? since : Math.max(f.since, since)
|
|
423
|
+
}));
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
/** AsyncIterable wrapper over a subscription's events until EOSE or close. */
|
|
427
|
+
function subscriptionToAsyncIterable(start, opts) {
|
|
428
|
+
const queue = [];
|
|
429
|
+
let head = 0;
|
|
430
|
+
let done = false;
|
|
431
|
+
let error;
|
|
432
|
+
let wake;
|
|
433
|
+
let closer;
|
|
434
|
+
let localClose = false;
|
|
435
|
+
let disposeAbort;
|
|
436
|
+
const notify = () => {
|
|
437
|
+
wake?.();
|
|
438
|
+
wake = void 0;
|
|
439
|
+
};
|
|
440
|
+
const waitForWake = async () => new Promise((resolve) => {
|
|
441
|
+
wake = resolve;
|
|
442
|
+
});
|
|
443
|
+
const closeLocal = (reason) => {
|
|
444
|
+
localClose = true;
|
|
445
|
+
disposeAbort?.();
|
|
446
|
+
closer?.close(reason);
|
|
447
|
+
done = true;
|
|
448
|
+
notify();
|
|
449
|
+
};
|
|
450
|
+
closer = start({
|
|
451
|
+
onevent(event) {
|
|
452
|
+
queue.push(event);
|
|
453
|
+
notify();
|
|
454
|
+
},
|
|
455
|
+
oneose() {
|
|
456
|
+
if (opts?.includeEose === false) closeLocal("eose");
|
|
457
|
+
},
|
|
458
|
+
onclose(reason) {
|
|
459
|
+
if (opts?.signal?.aborted === true) localClose = true;
|
|
460
|
+
if (!localClose && reason) error = new RelayClosedError(reason);
|
|
461
|
+
done = true;
|
|
462
|
+
notify();
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
if (opts?.signal) {
|
|
466
|
+
if (opts.signal.aborted) closeLocal("aborted");
|
|
467
|
+
else {
|
|
468
|
+
disposeAbort = onAbort(opts.signal, () => closeLocal("aborted"));
|
|
469
|
+
if (done) disposeAbort();
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return {
|
|
473
|
+
close(reason) {
|
|
474
|
+
closeLocal(reason ?? "closed by client");
|
|
475
|
+
},
|
|
476
|
+
[Symbol.asyncIterator]() {
|
|
477
|
+
return {
|
|
478
|
+
async next() {
|
|
479
|
+
while (true) {
|
|
480
|
+
const value = head < queue.length ? queue[head] : void 0;
|
|
481
|
+
if (value !== void 0) {
|
|
482
|
+
head += 1;
|
|
483
|
+
if (head === queue.length) {
|
|
484
|
+
queue.length = 0;
|
|
485
|
+
head = 0;
|
|
486
|
+
}
|
|
487
|
+
return {
|
|
488
|
+
value,
|
|
489
|
+
done: false
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
if (error !== void 0) throw error;
|
|
493
|
+
if (done) return {
|
|
494
|
+
value: void 0,
|
|
495
|
+
done: true
|
|
496
|
+
};
|
|
497
|
+
await waitForWake();
|
|
498
|
+
}
|
|
499
|
+
},
|
|
500
|
+
async return() {
|
|
501
|
+
closeLocal("iterator returned");
|
|
502
|
+
return {
|
|
503
|
+
value: void 0,
|
|
504
|
+
done: true
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
}
|
|
511
|
+
//#endregion
|
|
512
|
+
//#region src/relay/fetch.ts
|
|
513
|
+
function streamFilters(subscribe, filters, opts) {
|
|
514
|
+
return subscriptionToAsyncIterable((handlers) => subscribe(filters, {
|
|
515
|
+
...handlers,
|
|
516
|
+
id: opts?.id,
|
|
517
|
+
signal: opts?.signal
|
|
518
|
+
}), { signal: opts?.signal });
|
|
519
|
+
}
|
|
520
|
+
async function fetchFilters(subscribe, filters, opts) {
|
|
521
|
+
throwIfAborted(opts.signal);
|
|
522
|
+
const events = [];
|
|
523
|
+
const seen = /* @__PURE__ */ new Set();
|
|
524
|
+
let end = { type: "timeout" };
|
|
525
|
+
await new Promise((resolve, reject) => {
|
|
526
|
+
let settled = false;
|
|
527
|
+
const done = (err) => {
|
|
528
|
+
if (settled) return;
|
|
529
|
+
settled = true;
|
|
530
|
+
disposeAbort();
|
|
531
|
+
clearTimeout(timer);
|
|
532
|
+
let reason = "aborted";
|
|
533
|
+
if (err instanceof Error) reason = err.message;
|
|
534
|
+
else if (err === void 0) reason = "fetch complete";
|
|
535
|
+
sub.close(reason);
|
|
536
|
+
if (err === void 0) resolve();
|
|
537
|
+
else reject(err);
|
|
538
|
+
};
|
|
539
|
+
const disposeAbort = onAbort(opts.signal, () => {
|
|
540
|
+
if (opts.signal !== void 0) done(abortReason(opts.signal));
|
|
541
|
+
});
|
|
542
|
+
const timer = setTimeout(() => {
|
|
543
|
+
end = { type: "timeout" };
|
|
544
|
+
done();
|
|
545
|
+
}, opts.timeoutMs);
|
|
546
|
+
const sub = subscribe(filters, {
|
|
547
|
+
id: opts.id,
|
|
548
|
+
signal: opts.signal,
|
|
549
|
+
closeOnEose: true,
|
|
550
|
+
onevent(event) {
|
|
551
|
+
if (seen.has(event.id)) return;
|
|
552
|
+
seen.add(event.id);
|
|
553
|
+
events.push(event);
|
|
554
|
+
},
|
|
555
|
+
oneose() {
|
|
556
|
+
end = { type: "eose" };
|
|
557
|
+
done();
|
|
558
|
+
},
|
|
559
|
+
onclose(reason) {
|
|
560
|
+
if (settled) return;
|
|
561
|
+
if (opts.signal?.aborted === true) {
|
|
562
|
+
done(abortReason(opts.signal));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
end = {
|
|
566
|
+
type: "closed",
|
|
567
|
+
reason
|
|
568
|
+
};
|
|
569
|
+
done();
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
return {
|
|
574
|
+
events,
|
|
575
|
+
end
|
|
576
|
+
};
|
|
577
|
+
}
|
|
578
|
+
//#endregion
|
|
65
579
|
//#region src/relay/neg-session.ts
|
|
66
580
|
function createNegSession() {
|
|
67
581
|
return {
|
|
@@ -88,6 +602,21 @@ function failNegSession(session, err) {
|
|
|
88
602
|
function failNegErr(session, reason) {
|
|
89
603
|
failNegSession(session, new Nip77Error(reason));
|
|
90
604
|
}
|
|
605
|
+
/** Route a NEG-MSG/NEG-ERR frame to its session; unknown ids are ignored. */
|
|
606
|
+
function dispatchNegMessage(sessions, msg) {
|
|
607
|
+
const session = sessions.get(msg[1]);
|
|
608
|
+
if (session === void 0) return;
|
|
609
|
+
if (msg[0] === "NEG-MSG") {
|
|
610
|
+
pushNegMsg(session, msg[2]);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
failNegErr(session, msg[2]);
|
|
614
|
+
}
|
|
615
|
+
/** Fail every tracked session (socket teardown). */
|
|
616
|
+
function failAllNegSessions(sessions, err) {
|
|
617
|
+
for (const session of sessions.values()) failNegSession(session, err);
|
|
618
|
+
sessions.clear();
|
|
619
|
+
}
|
|
91
620
|
/** Wire queue/timeout/abort around `runNegSession`. Not a second session class. */
|
|
92
621
|
async function runWiredNegSession(opts) {
|
|
93
622
|
const { session, storage, filter, id, timeoutMs, signal, send, url } = opts;
|
|
@@ -144,6 +673,175 @@ async function runWiredNegSession(opts) {
|
|
|
144
673
|
next
|
|
145
674
|
});
|
|
146
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* Drive a NIP-77 reconcile session against the session map: fail a same-id predecessor, register,
|
|
678
|
+
* wire queue/timeout/abort, and send NEG-CLOSE on exit.
|
|
679
|
+
*/
|
|
680
|
+
async function runTrackedNegSession(opts) {
|
|
681
|
+
const { sessions, id, send } = opts;
|
|
682
|
+
const prev = sessions.get(id);
|
|
683
|
+
if (prev) failNegSession(prev, new Nip77Error("closed: replaced by new NEG-OPEN"));
|
|
684
|
+
const session = createNegSession();
|
|
685
|
+
sessions.set(id, session);
|
|
686
|
+
try {
|
|
687
|
+
return await runWiredNegSession({
|
|
688
|
+
session,
|
|
689
|
+
storage: opts.storage,
|
|
690
|
+
filter: opts.filter,
|
|
691
|
+
id,
|
|
692
|
+
timeoutMs: opts.timeoutMs,
|
|
693
|
+
signal: opts.signal,
|
|
694
|
+
send,
|
|
695
|
+
url: opts.url
|
|
696
|
+
});
|
|
697
|
+
} finally {
|
|
698
|
+
if (sessions.get(id) === session) {
|
|
699
|
+
sessions.delete(id);
|
|
700
|
+
try {
|
|
701
|
+
send(["NEG-CLOSE", id]);
|
|
702
|
+
} catch {}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
//#endregion
|
|
707
|
+
//#region src/relay/ok-tracker.ts
|
|
708
|
+
var OkTracker = class {
|
|
709
|
+
#entries = /* @__PURE__ */ new Map();
|
|
710
|
+
get size() {
|
|
711
|
+
return this.#entries.size;
|
|
712
|
+
}
|
|
713
|
+
get(eventId) {
|
|
714
|
+
return this.#entries.get(eventId);
|
|
715
|
+
}
|
|
716
|
+
/** Joiner's view: only publish waiters (carrying an `event`) are joinable. */
|
|
717
|
+
inFlight(eventId) {
|
|
718
|
+
const waiter = this.#entries.get(eventId);
|
|
719
|
+
return waiter?.event === void 0 ? void 0 : waiter.promise;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Register a waiter, arm its timeout and call `send`. A send throw removes the entry and rejects
|
|
723
|
+
* (`sendError` when the throw is not an Error). The timer only rejects while this waiter still
|
|
724
|
+
* owns the id.
|
|
725
|
+
*/
|
|
726
|
+
async track(eventId, opts) {
|
|
727
|
+
const promise = new Promise((resolve, reject) => {
|
|
728
|
+
const waiter = {
|
|
729
|
+
id: eventId,
|
|
730
|
+
resolve,
|
|
731
|
+
reject,
|
|
732
|
+
timer: setTimeout(() => {
|
|
733
|
+
if (this.#entries.get(eventId) === waiter) {
|
|
734
|
+
this.#entries.delete(eventId);
|
|
735
|
+
reject(opts.timeoutError());
|
|
736
|
+
}
|
|
737
|
+
}, opts.timeoutMs),
|
|
738
|
+
event: opts.event,
|
|
739
|
+
authRetried: false,
|
|
740
|
+
timeoutMs: opts.timeoutMs,
|
|
741
|
+
timeoutError: opts.timeoutError,
|
|
742
|
+
get promise() {
|
|
743
|
+
return promise;
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
this.#entries.set(eventId, waiter);
|
|
747
|
+
try {
|
|
748
|
+
opts.send();
|
|
749
|
+
} catch (error) {
|
|
750
|
+
this.#finish(waiter);
|
|
751
|
+
reject(error instanceof Error ? error : opts.sendError());
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
return promise;
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* An OK reply arrived: `retry` is the caller's auth-required policy — a kept waiter is returned
|
|
758
|
+
* unsettled for the retry path; otherwise the entry is settled and undefined is returned.
|
|
759
|
+
*/
|
|
760
|
+
handleOk(eventId, ok, message, retry) {
|
|
761
|
+
const waiter = this.#entries.get(eventId);
|
|
762
|
+
if (waiter === void 0) return;
|
|
763
|
+
if (!retry(waiter)) {
|
|
764
|
+
this.settle(waiter, {
|
|
765
|
+
ok,
|
|
766
|
+
message
|
|
767
|
+
});
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
return waiter;
|
|
771
|
+
}
|
|
772
|
+
/** Settle a waiter: stop its timer, drop the entry it still owns, then resolve. */
|
|
773
|
+
settle(waiter, result) {
|
|
774
|
+
this.#finish(waiter);
|
|
775
|
+
waiter.resolve(result);
|
|
776
|
+
}
|
|
777
|
+
/** Stop a waiter's timeout without settling (AUTH retry window). */
|
|
778
|
+
clearTimer(waiter) {
|
|
779
|
+
if (waiter.timer !== void 0) {
|
|
780
|
+
clearTimeout(waiter.timer);
|
|
781
|
+
waiter.timer = void 0;
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
/** Re-arm the timeout for a re-sent frame (after an AUTH retry). */
|
|
785
|
+
restartTimer(eventId) {
|
|
786
|
+
const waiter = this.#entries.get(eventId);
|
|
787
|
+
if (waiter === void 0) return;
|
|
788
|
+
waiter.timer = setTimeout(() => {
|
|
789
|
+
if (this.#entries.get(eventId) === waiter) {
|
|
790
|
+
this.#entries.delete(eventId);
|
|
791
|
+
waiter.reject(waiter.timeoutError());
|
|
792
|
+
}
|
|
793
|
+
}, waiter.timeoutMs);
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Re-send a tracked EVENT after the relay demanded AUTH: pause the timeout through the AUTH
|
|
797
|
+
* window, re-arm it and re-send on success, settle `{ok:false,message}` on failure. AUTH waiters
|
|
798
|
+
* (no `event`) settle immediately.
|
|
799
|
+
*/
|
|
800
|
+
async authRetry(waiter, message, deps) {
|
|
801
|
+
this.clearTimer(waiter);
|
|
802
|
+
const finish = (result) => {
|
|
803
|
+
this.settle(waiter, result);
|
|
804
|
+
};
|
|
805
|
+
try {
|
|
806
|
+
const { event } = waiter;
|
|
807
|
+
if (event === void 0) {
|
|
808
|
+
finish({
|
|
809
|
+
ok: false,
|
|
810
|
+
message
|
|
811
|
+
});
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
const ok = await deps.ensureAuthed();
|
|
815
|
+
if (this.#entries.get(waiter.id) !== waiter) return;
|
|
816
|
+
if (!ok || !deps.isOpen()) {
|
|
817
|
+
finish({
|
|
818
|
+
ok: false,
|
|
819
|
+
message
|
|
820
|
+
});
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
this.restartTimer(waiter.id);
|
|
824
|
+
deps.send(event);
|
|
825
|
+
} catch {
|
|
826
|
+
if (this.#entries.get(waiter.id) === void 0) return;
|
|
827
|
+
finish({
|
|
828
|
+
ok: false,
|
|
829
|
+
message
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
rejectAll(err) {
|
|
834
|
+
for (const waiter of this.#entries.values()) {
|
|
835
|
+
this.clearTimer(waiter);
|
|
836
|
+
waiter.reject(err);
|
|
837
|
+
}
|
|
838
|
+
this.#entries.clear();
|
|
839
|
+
}
|
|
840
|
+
#finish(waiter) {
|
|
841
|
+
this.clearTimer(waiter);
|
|
842
|
+
if (this.#entries.get(waiter.id) === waiter) this.#entries.delete(waiter.id);
|
|
843
|
+
}
|
|
844
|
+
};
|
|
147
845
|
const PING_DUMMY_FILTER = {
|
|
148
846
|
ids: ["a".repeat(64)],
|
|
149
847
|
limit: 0
|
|
@@ -266,458 +964,254 @@ var PingLoop = class {
|
|
|
266
964
|
}
|
|
267
965
|
};
|
|
268
966
|
//#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
|
-
}
|
|
967
|
+
//#region src/relay/subscription-registry.ts
|
|
968
|
+
/**
|
|
969
|
+
* Owns every open REQ of a relay: exclusive subscriptions keyed by id, and live coalescing groups
|
|
970
|
+
* (one wire REQ plus per-subscriber handles) keyed by fingerprint and wire id.
|
|
971
|
+
*/
|
|
972
|
+
var SubscriptionRegistry = class {
|
|
973
|
+
#subs = /* @__PURE__ */ new Map();
|
|
974
|
+
#liveByFp = /* @__PURE__ */ new Map();
|
|
975
|
+
#liveBySubId = /* @__PURE__ */ new Map();
|
|
976
|
+
#isOpen;
|
|
977
|
+
#enableReconnect;
|
|
978
|
+
#send;
|
|
979
|
+
#scheduleReconnect;
|
|
980
|
+
#acceptEvent;
|
|
981
|
+
constructor(deps) {
|
|
982
|
+
this.#isOpen = deps.isOpen;
|
|
983
|
+
this.#enableReconnect = deps.enableReconnect;
|
|
984
|
+
this.#send = deps.send;
|
|
985
|
+
this.#scheduleReconnect = deps.scheduleReconnect;
|
|
986
|
+
this.#acceptEvent = deps.acceptEvent;
|
|
301
987
|
}
|
|
302
|
-
/**
|
|
303
|
-
|
|
304
|
-
this.#
|
|
988
|
+
/** User subscriptions only; dummy ping REQs are not counted. */
|
|
989
|
+
get size() {
|
|
990
|
+
return this.#subs.size;
|
|
305
991
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
this.closed = true;
|
|
309
|
-
this.dispose();
|
|
310
|
-
this.#sendClose(this.id);
|
|
311
|
-
invokeSafely(() => this.handlers.onclose?.(reason));
|
|
992
|
+
get(subId) {
|
|
993
|
+
return this.#subs.get(subId);
|
|
312
994
|
}
|
|
313
|
-
/**
|
|
314
|
-
|
|
315
|
-
if (
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
995
|
+
/** Exclusive one-shot REQ when `closeOnEose`, live coalescing otherwise. */
|
|
996
|
+
open(filters, opts) {
|
|
997
|
+
if (opts.closeOnEose === true) return this.#openExclusive(filters, opts);
|
|
998
|
+
return this.#openLive(filters, opts);
|
|
999
|
+
}
|
|
1000
|
+
handleEvent(subId, event) {
|
|
1001
|
+
const sub = this.#subs.get(subId);
|
|
1002
|
+
if (!sub || sub.closed) return;
|
|
1003
|
+
const group = this.#liveBySubId.get(subId);
|
|
1004
|
+
if (group) {
|
|
1005
|
+
this.#deliverLiveEvent(group, event);
|
|
319
1006
|
return;
|
|
320
1007
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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();
|
|
1008
|
+
sub.notifyReceived(event.id);
|
|
1009
|
+
if (sub.isAtWatermark(event.id)) return;
|
|
1010
|
+
if (sub.alreadyHas(event.id)) return;
|
|
1011
|
+
if (!matchFilters(sub.filters, event)) return;
|
|
1012
|
+
if (!this.#acceptEvent(event)) return;
|
|
1013
|
+
sub.deliver(event);
|
|
1014
|
+
}
|
|
1015
|
+
handleEose(subId) {
|
|
1016
|
+
const sub = this.#subs.get(subId);
|
|
1017
|
+
if (!sub || sub.closed || sub.eosed) return;
|
|
1018
|
+
const group = this.#liveBySubId.get(subId);
|
|
1019
|
+
if (group) {
|
|
1020
|
+
sub.markEose();
|
|
1021
|
+
this.#deliverLiveEose(group);
|
|
1022
|
+
return;
|
|
372
1023
|
}
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
1024
|
+
sub.markEose();
|
|
1025
|
+
}
|
|
1026
|
+
/** Remote/transport termination of a REQ (CLOSED, auth-retry failure). */
|
|
1027
|
+
drop(sub, reason) {
|
|
1028
|
+
const group = this.#liveBySubId.get(sub.id);
|
|
1029
|
+
if (group) {
|
|
1030
|
+
this.#endLiveGroup(group, {
|
|
1031
|
+
sendClose: false,
|
|
1032
|
+
reason
|
|
1033
|
+
});
|
|
1034
|
+
return;
|
|
379
1035
|
}
|
|
1036
|
+
this.#subs.delete(sub.id);
|
|
1037
|
+
sub.end(reason);
|
|
380
1038
|
}
|
|
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);
|
|
1039
|
+
/**
|
|
1040
|
+
* Re-REQ after the relay demanded AUTH: drop the subscription when auth fails or the
|
|
1041
|
+
* socket/subscription died during the AUTH window, replay the REQ on success.
|
|
1042
|
+
*/
|
|
1043
|
+
async authRetry(sub, reason, ensureAuthed) {
|
|
419
1044
|
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;
|
|
1045
|
+
if (!await ensureAuthed() || sub.closed || !this.#isOpen()) {
|
|
1046
|
+
this.drop(sub, reason);
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
this.replay(sub);
|
|
1050
|
+
} catch {
|
|
1051
|
+
this.drop(sub, reason);
|
|
1052
|
+
}
|
|
472
1053
|
}
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
1054
|
+
/** Re-REQ after AUTH: rearm the wire and its handles, then send the replayed filters. */
|
|
1055
|
+
replay(sub) {
|
|
1056
|
+
sub.rearm();
|
|
1057
|
+
const group = this.#liveBySubId.get(sub.id);
|
|
1058
|
+
if (group) for (const att of group.attachments) att.rearm();
|
|
1059
|
+
this.#send([
|
|
476
1060
|
"REQ",
|
|
477
|
-
|
|
478
|
-
...
|
|
1061
|
+
sub.id,
|
|
1062
|
+
...sub.replayFilters()
|
|
479
1063
|
]);
|
|
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
1064
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
1065
|
+
/** Reconnect path: rearm every open REQ and re-send; false on send failure. */
|
|
1066
|
+
replayAll() {
|
|
1067
|
+
for (const sub of this.#subs.values()) {
|
|
1068
|
+
if (sub.closed) continue;
|
|
1069
|
+
sub.rearm();
|
|
1070
|
+
sub.resetAuthRetry();
|
|
1071
|
+
const group = this.#liveBySubId.get(sub.id);
|
|
1072
|
+
if (group) for (const att of group.attachments) {
|
|
1073
|
+
att.rearm();
|
|
1074
|
+
att.resetAuthRetry();
|
|
1075
|
+
}
|
|
1076
|
+
try {
|
|
1077
|
+
this.#send([
|
|
1078
|
+
"REQ",
|
|
1079
|
+
sub.id,
|
|
1080
|
+
...sub.replayFilters()
|
|
1081
|
+
]);
|
|
1082
|
+
} catch {
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
633
1085
|
}
|
|
1086
|
+
return true;
|
|
634
1087
|
}
|
|
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));
|
|
1088
|
+
closeAll(reason) {
|
|
1089
|
+
const groups = [...this.#liveBySubId.values()];
|
|
1090
|
+
for (const group of groups) invokeSafely(() => {
|
|
1091
|
+
this.#endLiveGroup(group, {
|
|
1092
|
+
sendClose: false,
|
|
1093
|
+
reason
|
|
1094
|
+
});
|
|
665
1095
|
});
|
|
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
|
-
}
|
|
1096
|
+
for (const sub of this.#subs.values()) sub.end(reason);
|
|
1097
|
+
this.#subs.clear();
|
|
1098
|
+
this.#liveByFp.clear();
|
|
1099
|
+
this.#liveBySubId.clear();
|
|
1100
|
+
}
|
|
1101
|
+
#openExclusive(filters, opts) {
|
|
1102
|
+
const sub = new Subscription(filters, opts, (closed) => {
|
|
1103
|
+
this.#subs.delete(closed.id);
|
|
1104
|
+
try {
|
|
1105
|
+
if (this.#isOpen()) this.#send(["CLOSE", closed.id]);
|
|
1106
|
+
} catch {}
|
|
1107
|
+
});
|
|
1108
|
+
this.#subs.set(sub.id, sub);
|
|
1109
|
+
if (this.#isOpen()) this.#send([
|
|
1110
|
+
"REQ",
|
|
1111
|
+
sub.id,
|
|
1112
|
+
...filters
|
|
1113
|
+
]);
|
|
1114
|
+
else if (this.#enableReconnect) this.#scheduleReconnect();
|
|
1115
|
+
return sub;
|
|
1116
|
+
}
|
|
1117
|
+
#openLive(filters, opts) {
|
|
1118
|
+
const fp = filterFingerprint(filters);
|
|
1119
|
+
let group = this.#liveByFp.get(fp);
|
|
1120
|
+
if (group?.sub.eosed === true) group = void 0;
|
|
1121
|
+
let created = false;
|
|
1122
|
+
if (!group) {
|
|
1123
|
+
created = true;
|
|
1124
|
+
const slot = { group: void 0 };
|
|
1125
|
+
const wire = new Subscription(filters, { id: opts.id }, () => {
|
|
1126
|
+
const g = slot.group;
|
|
1127
|
+
if (g) this.#endLiveGroup(g, {
|
|
1128
|
+
sendClose: true,
|
|
1129
|
+
reason: "closed by client"
|
|
1130
|
+
});
|
|
1131
|
+
});
|
|
1132
|
+
group = {
|
|
1133
|
+
fp,
|
|
1134
|
+
sub: wire,
|
|
1135
|
+
attachments: /* @__PURE__ */ new Set()
|
|
1136
|
+
};
|
|
1137
|
+
slot.group = group;
|
|
1138
|
+
this.#liveByFp.set(fp, group);
|
|
1139
|
+
this.#liveBySubId.set(wire.id, group);
|
|
1140
|
+
this.#subs.set(wire.id, wire);
|
|
1141
|
+
}
|
|
1142
|
+
const slot = { handle: void 0 };
|
|
1143
|
+
const attached = group;
|
|
1144
|
+
const handle = new Subscription(filters, {
|
|
1145
|
+
...opts,
|
|
1146
|
+
id: group.sub.id
|
|
1147
|
+
}, () => {
|
|
1148
|
+
const current = slot.handle;
|
|
1149
|
+
if (!current) return;
|
|
1150
|
+
this.#detachLive(attached, current);
|
|
695
1151
|
});
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
1152
|
+
slot.handle = handle;
|
|
1153
|
+
if (handle.closed) {
|
|
1154
|
+
if (created && group.attachments.size === 0) this.#forgetLiveGroup(group, "closed by client");
|
|
1155
|
+
return handle;
|
|
1156
|
+
}
|
|
1157
|
+
group.attachments.add(handle);
|
|
1158
|
+
if (created) {
|
|
1159
|
+
if (this.#isOpen()) this.#send([
|
|
1160
|
+
"REQ",
|
|
1161
|
+
group.sub.id,
|
|
1162
|
+
...filters
|
|
1163
|
+
]);
|
|
1164
|
+
else if (this.#enableReconnect) this.#scheduleReconnect();
|
|
1165
|
+
}
|
|
1166
|
+
return handle;
|
|
1167
|
+
}
|
|
1168
|
+
#forgetLiveGroup(group, reason) {
|
|
1169
|
+
if (this.#liveByFp.get(group.fp) === group) this.#liveByFp.delete(group.fp);
|
|
1170
|
+
this.#liveBySubId.delete(group.sub.id);
|
|
1171
|
+
this.#subs.delete(group.sub.id);
|
|
1172
|
+
group.sub.end(reason);
|
|
1173
|
+
}
|
|
1174
|
+
#detachLive(group, handle) {
|
|
1175
|
+
if (!group.attachments.delete(handle)) return;
|
|
1176
|
+
if (group.attachments.size > 0) return;
|
|
1177
|
+
this.#endLiveGroup(group, {
|
|
1178
|
+
sendClose: true,
|
|
1179
|
+
reason: "closed by client"
|
|
708
1180
|
});
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
}
|
|
1181
|
+
}
|
|
1182
|
+
#endLiveGroup(group, opts) {
|
|
1183
|
+
const { id } = group.sub;
|
|
1184
|
+
const remaining = [...group.attachments];
|
|
1185
|
+
group.attachments.clear();
|
|
1186
|
+
this.#forgetLiveGroup(group, opts.reason);
|
|
1187
|
+
if (opts.sendClose) try {
|
|
1188
|
+
if (this.#isOpen()) this.#send(["CLOSE", id]);
|
|
1189
|
+
} catch {}
|
|
1190
|
+
for (const att of remaining) invokeSafely(() => {
|
|
1191
|
+
att.close(opts.reason);
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
#deliverLiveEvent(group, event) {
|
|
1195
|
+
const { sub } = group;
|
|
1196
|
+
const attachments = [...group.attachments];
|
|
1197
|
+
for (const att of attachments) att.notifyReceived(event.id);
|
|
1198
|
+
if (sub.isAtWatermark(event.id)) return;
|
|
1199
|
+
const recipients = [];
|
|
1200
|
+
for (const att of attachments) {
|
|
1201
|
+
if (att.closed) continue;
|
|
1202
|
+
if (!att.alreadyHas(event.id)) recipients.push(att);
|
|
1203
|
+
}
|
|
1204
|
+
if (recipients.length === 0) return;
|
|
1205
|
+
if (!matchFilters(sub.filters, event)) return;
|
|
1206
|
+
if (!this.#acceptEvent(event)) return;
|
|
1207
|
+
sub.noteVerified(event);
|
|
1208
|
+
for (const att of recipients) att.deliver(event);
|
|
1209
|
+
}
|
|
1210
|
+
#deliverLiveEose(group) {
|
|
1211
|
+
const attachments = [...group.attachments];
|
|
1212
|
+
for (const att of attachments) att.markEose();
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
721
1215
|
//#endregion
|
|
722
1216
|
//#region src/relay/relay.ts
|
|
723
1217
|
/**
|
|
@@ -762,12 +1256,11 @@ var Relay = class Relay {
|
|
|
762
1256
|
#connectFinish;
|
|
763
1257
|
#connectTimer;
|
|
764
1258
|
#socketHandlers;
|
|
765
|
-
#
|
|
766
|
-
#
|
|
767
|
-
#
|
|
768
|
-
#
|
|
769
|
-
#
|
|
770
|
-
#counts = /* @__PURE__ */ new Map();
|
|
1259
|
+
#subscriptions;
|
|
1260
|
+
#ok = new OkTracker();
|
|
1261
|
+
#countRequests = new CountTracker();
|
|
1262
|
+
#authState;
|
|
1263
|
+
#ensureAuthed;
|
|
771
1264
|
#neg = /* @__PURE__ */ new Map();
|
|
772
1265
|
#WS;
|
|
773
1266
|
#verify;
|
|
@@ -777,13 +1270,6 @@ var Relay = class Relay {
|
|
|
777
1270
|
#enableReconnect;
|
|
778
1271
|
#backoff;
|
|
779
1272
|
#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
1273
|
#authSigner;
|
|
788
1274
|
/** `close()`/`disconnect()` was called; cleared by the next `connect()`. */
|
|
789
1275
|
#manualStop = false;
|
|
@@ -791,13 +1277,7 @@ var Relay = class Relay {
|
|
|
791
1277
|
#reconnectTimer;
|
|
792
1278
|
#enablePing;
|
|
793
1279
|
#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
|
-
};
|
|
1280
|
+
#events = new Emitter();
|
|
801
1281
|
constructor(url, opts = {}) {
|
|
802
1282
|
this.url = normalizeURL(url);
|
|
803
1283
|
this.#WS = opts.websocketImplementation ?? getWebSocketImplementation();
|
|
@@ -821,21 +1301,29 @@ var Relay = class Relay {
|
|
|
821
1301
|
pingIntervalMs: opts.pingIntervalMs ?? 29e3,
|
|
822
1302
|
pingTimeoutMs: opts.pingTimeoutMs ?? 2e4
|
|
823
1303
|
});
|
|
824
|
-
this.#
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
subs: this.#subs,
|
|
828
|
-
connected: () => this.#isOpen(),
|
|
829
|
-
enableReconnect: () => this.#enableReconnect,
|
|
1304
|
+
this.#subscriptions = new SubscriptionRegistry({
|
|
1305
|
+
isOpen: () => this.#isOpen(),
|
|
1306
|
+
enableReconnect: this.#enableReconnect,
|
|
830
1307
|
send: (message) => this.#send(message),
|
|
831
1308
|
scheduleReconnect: () => this.#scheduleReconnect(),
|
|
832
|
-
acceptEvent: (event) => this.#acceptEvent(event)
|
|
833
|
-
|
|
834
|
-
|
|
1309
|
+
acceptEvent: (event) => this.#acceptEvent(event)
|
|
1310
|
+
});
|
|
1311
|
+
this.#authState = new RelayAuth({
|
|
1312
|
+
url: this.url,
|
|
1313
|
+
isOpen: () => this.#isOpen(),
|
|
1314
|
+
sendAuth: (event, timeoutMs) => this.#ok.track(event.id, {
|
|
1315
|
+
timeoutMs,
|
|
1316
|
+
timeoutError: () => new RelayTimeoutError("auth timed out", this.url),
|
|
1317
|
+
sendError: () => new RelayPublishError("auth send failed", this.url),
|
|
1318
|
+
send: () => this.#send(["AUTH", event])
|
|
1319
|
+
}),
|
|
1320
|
+
defaultTimeoutMs: this.#publishTimeoutMs
|
|
1321
|
+
});
|
|
1322
|
+
this.#ensureAuthed = this.#authState.ensureAuthed.bind(this.#authState, this.#authSigner);
|
|
835
1323
|
}
|
|
836
1324
|
/** Latest NIP-42 challenge, if any. */
|
|
837
1325
|
get challenge() {
|
|
838
|
-
return this.#challenge;
|
|
1326
|
+
return this.#authState.challenge;
|
|
839
1327
|
}
|
|
840
1328
|
get connected() {
|
|
841
1329
|
return this.#isOpen();
|
|
@@ -857,11 +1345,11 @@ var Relay = class Relay {
|
|
|
857
1345
|
}
|
|
858
1346
|
/** User subscriptions only; dummy ping REQs are not counted. */
|
|
859
1347
|
get subscriptionCount() {
|
|
860
|
-
return this.#
|
|
1348
|
+
return this.#subscriptions.size;
|
|
861
1349
|
}
|
|
862
1350
|
/** One-shot requests still awaiting a relay reply (EVENT ACK, COUNT, NEG). */
|
|
863
1351
|
get inFlightCount() {
|
|
864
|
-
return this.#
|
|
1352
|
+
return this.#ok.size + this.#countRequests.size + this.#neg.size;
|
|
865
1353
|
}
|
|
866
1354
|
/**
|
|
867
1355
|
* Listen for a relay event. Multiple listeners per type are allowed and fire in registration
|
|
@@ -869,14 +1357,7 @@ var Relay = class Relay {
|
|
|
869
1357
|
* function.
|
|
870
1358
|
*/
|
|
871
1359
|
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));
|
|
1360
|
+
return this.#events.on(type, listener);
|
|
880
1361
|
}
|
|
881
1362
|
static async connect(url, opts) {
|
|
882
1363
|
const relay = new Relay(url, opts);
|
|
@@ -912,6 +1393,15 @@ var Relay = class Relay {
|
|
|
912
1393
|
} catch {}
|
|
913
1394
|
if (this.#ws === ws) this.#ws = void 0;
|
|
914
1395
|
};
|
|
1396
|
+
const guarded = (fn) => {
|
|
1397
|
+
return () => {
|
|
1398
|
+
if (gen !== this.#gen) {
|
|
1399
|
+
release();
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
fn();
|
|
1403
|
+
};
|
|
1404
|
+
};
|
|
915
1405
|
const finish = (err) => {
|
|
916
1406
|
if (settled) return;
|
|
917
1407
|
settled = true;
|
|
@@ -924,18 +1414,14 @@ var Relay = class Relay {
|
|
|
924
1414
|
};
|
|
925
1415
|
this.#connecting = connecting;
|
|
926
1416
|
this.#connectFinish = finish;
|
|
927
|
-
const timer = setTimeout(() => {
|
|
928
|
-
if (gen !== this.#gen) {
|
|
929
|
-
release();
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
1417
|
+
const timer = setTimeout(guarded(() => {
|
|
932
1418
|
release();
|
|
933
1419
|
finish(new RelayTimeoutError("connection timed out", this.url));
|
|
934
1420
|
if (gen === this.#gen) this.#handleSocketDeath("connection timed out", {
|
|
935
1421
|
fromConnectAttempt: true,
|
|
936
1422
|
gen
|
|
937
1423
|
});
|
|
938
|
-
}, timeoutMs);
|
|
1424
|
+
}), timeoutMs);
|
|
939
1425
|
this.#connectTimer = timer;
|
|
940
1426
|
try {
|
|
941
1427
|
ws = new this.#WS(this.url);
|
|
@@ -949,36 +1435,24 @@ var Relay = class Relay {
|
|
|
949
1435
|
return;
|
|
950
1436
|
}
|
|
951
1437
|
this.#ws = ws;
|
|
952
|
-
const onOpen = () => {
|
|
953
|
-
if (gen !== this.#gen) {
|
|
954
|
-
release();
|
|
955
|
-
return;
|
|
956
|
-
}
|
|
1438
|
+
const onOpen = guarded(() => {
|
|
957
1439
|
this.#setStatus(RelayStatus.Connected);
|
|
958
1440
|
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)) {
|
|
1441
|
+
this.#authState.resetConnection();
|
|
1442
|
+
if (!this.#subscriptions.replayAll()) {
|
|
965
1443
|
this.#setStatus(RelayStatus.Disconnected);
|
|
966
1444
|
release();
|
|
967
1445
|
finish(new RelayConnectionError("connection failed", this.url));
|
|
968
|
-
if (this.#enableReconnect && !this.#manualStop && this.#
|
|
1446
|
+
if (this.#enableReconnect && !this.#manualStop && this.#subscriptions.size > 0) this.#scheduleReconnect();
|
|
969
1447
|
return;
|
|
970
1448
|
}
|
|
971
1449
|
this.#reconnectAttempts = 0;
|
|
972
1450
|
if (this.#enablePing) this.#ping.start();
|
|
973
1451
|
else this.#ping.stop();
|
|
974
|
-
if (wasReconnect) this.#emit("reconnect", void 0);
|
|
1452
|
+
if (wasReconnect) this.#events.emit("reconnect", void 0);
|
|
975
1453
|
finish();
|
|
976
|
-
};
|
|
977
|
-
const onError = () => {
|
|
978
|
-
if (gen !== this.#gen) {
|
|
979
|
-
release();
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
1454
|
+
});
|
|
1455
|
+
const onError = guarded(() => {
|
|
982
1456
|
const fromConnectAttempt = !settled;
|
|
983
1457
|
release();
|
|
984
1458
|
finish(new RelayConnectionError("connection failed", this.url));
|
|
@@ -986,12 +1460,8 @@ var Relay = class Relay {
|
|
|
986
1460
|
fromConnectAttempt,
|
|
987
1461
|
gen
|
|
988
1462
|
});
|
|
989
|
-
};
|
|
990
|
-
const onClose = () => {
|
|
991
|
-
if (gen !== this.#gen) {
|
|
992
|
-
release();
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
1463
|
+
});
|
|
1464
|
+
const onClose = guarded(() => {
|
|
995
1465
|
const fromConnectAttempt = !settled;
|
|
996
1466
|
release();
|
|
997
1467
|
if (fromConnectAttempt) finish(new RelayConnectionError("websocket closed", this.url));
|
|
@@ -999,7 +1469,7 @@ var Relay = class Relay {
|
|
|
999
1469
|
fromConnectAttempt,
|
|
1000
1470
|
gen
|
|
1001
1471
|
});
|
|
1002
|
-
};
|
|
1472
|
+
});
|
|
1003
1473
|
const onMessage = (ev) => {
|
|
1004
1474
|
if (gen !== this.#gen) return;
|
|
1005
1475
|
const data = typeof ev === "object" && ev !== null && "data" in ev ? ev.data : ev;
|
|
@@ -1046,12 +1516,12 @@ var Relay = class Relay {
|
|
|
1046
1516
|
this.#ping.stop();
|
|
1047
1517
|
this.#connectFinish?.(new RelayClosedError("relay closed", this.url));
|
|
1048
1518
|
try {
|
|
1049
|
-
|
|
1519
|
+
this.#subscriptions.closeAll("relay closed");
|
|
1050
1520
|
} finally {
|
|
1051
1521
|
this.#failPending(new RelayClosedError("relay closed", this.url));
|
|
1052
1522
|
this.#detachSocketHandlers();
|
|
1053
1523
|
this.#teardownSocket();
|
|
1054
|
-
this.#emit("close", void 0);
|
|
1524
|
+
this.#events.emit("close", void 0);
|
|
1055
1525
|
}
|
|
1056
1526
|
}
|
|
1057
1527
|
/**
|
|
@@ -1086,28 +1556,10 @@ var Relay = class Relay {
|
|
|
1086
1556
|
this.#reconnectTimer = void 0;
|
|
1087
1557
|
}
|
|
1088
1558
|
}
|
|
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
1559
|
#failPending(err) {
|
|
1108
|
-
this.#
|
|
1109
|
-
this.#
|
|
1110
|
-
this.#
|
|
1560
|
+
this.#ok.rejectAll(err);
|
|
1561
|
+
this.#countRequests.rejectAll(err);
|
|
1562
|
+
failAllNegSessions(this.#neg, err);
|
|
1111
1563
|
}
|
|
1112
1564
|
/** Unexpected socket death. Keep subscriptions if reconnecting. */
|
|
1113
1565
|
#handleSocketDeath(reason, opts) {
|
|
@@ -1117,15 +1569,15 @@ var Relay = class Relay {
|
|
|
1117
1569
|
this.#detachSocketHandlers();
|
|
1118
1570
|
this.#ws = void 0;
|
|
1119
1571
|
this.#failPending(new RelayClosedError(reason, this.url));
|
|
1120
|
-
if (this.#enableReconnect && !this.#manualStop && this.#
|
|
1572
|
+
if (this.#enableReconnect && !this.#manualStop && this.#subscriptions.size > 0) {
|
|
1121
1573
|
this.#setStatus(RelayStatus.Disconnected);
|
|
1122
1574
|
this.#scheduleReconnect();
|
|
1123
1575
|
return;
|
|
1124
1576
|
}
|
|
1125
1577
|
this.#setStatus(RelayStatus.Disconnected);
|
|
1126
|
-
if (opts.fromConnectAttempt !== true || this.#
|
|
1127
|
-
|
|
1128
|
-
if (!this.#manualStop) this.#emit("close", void 0);
|
|
1578
|
+
if (opts.fromConnectAttempt !== true || this.#subscriptions.size > 0) {
|
|
1579
|
+
this.#subscriptions.closeAll(reason);
|
|
1580
|
+
if (!this.#manualStop) this.#events.emit("close", void 0);
|
|
1129
1581
|
}
|
|
1130
1582
|
this.#setStatus(RelayStatus.Closed);
|
|
1131
1583
|
}
|
|
@@ -1141,7 +1593,7 @@ var Relay = class Relay {
|
|
|
1141
1593
|
try {
|
|
1142
1594
|
await this.connect();
|
|
1143
1595
|
} catch {
|
|
1144
|
-
if (this.#enableReconnect && !this.#manualStop && this.#
|
|
1596
|
+
if (this.#enableReconnect && !this.#manualStop && this.#subscriptions.size > 0 && !this.#isOpen()) this.#scheduleReconnect();
|
|
1145
1597
|
}
|
|
1146
1598
|
})();
|
|
1147
1599
|
}, delay);
|
|
@@ -1162,91 +1614,78 @@ var Relay = class Relay {
|
|
|
1162
1614
|
case "EVENT": {
|
|
1163
1615
|
const [, subId, event] = msg;
|
|
1164
1616
|
if (this.#ping.hasWaiter(subId)) return;
|
|
1165
|
-
|
|
1617
|
+
this.#subscriptions.handleEvent(subId, event);
|
|
1166
1618
|
break;
|
|
1167
1619
|
}
|
|
1168
1620
|
case "EOSE": {
|
|
1169
1621
|
const [, subId] = msg;
|
|
1170
1622
|
if (this.#ping.finishDummyPing(subId)) return;
|
|
1171
|
-
|
|
1623
|
+
this.#subscriptions.handleEose(subId);
|
|
1172
1624
|
break;
|
|
1173
1625
|
}
|
|
1174
1626
|
case "CLOSED": {
|
|
1175
1627
|
const [, subId, reason] = msg;
|
|
1176
1628
|
if (this.#ping.finishDummyPing(subId)) return;
|
|
1177
|
-
const countWaiter = this.#
|
|
1629
|
+
const countWaiter = this.#countRequests.get(subId);
|
|
1178
1630
|
if (countWaiter) {
|
|
1179
1631
|
if (isAuthRequired(reason) && this.#authSigner && !countWaiter.authRetried) {
|
|
1180
1632
|
countWaiter.authRetried = true;
|
|
1181
|
-
this.#
|
|
1633
|
+
this.#countRequests.authRetry(countWaiter, {
|
|
1634
|
+
ensureAuthed: this.#ensureAuthed,
|
|
1635
|
+
isOpen: () => this.#isOpen(),
|
|
1636
|
+
failError: () => new RelayClosedError(reason || "COUNT closed", this.url),
|
|
1637
|
+
send: () => this.#send([
|
|
1638
|
+
"COUNT",
|
|
1639
|
+
subId,
|
|
1640
|
+
...countWaiter.filters
|
|
1641
|
+
])
|
|
1642
|
+
});
|
|
1182
1643
|
return;
|
|
1183
1644
|
}
|
|
1184
1645
|
countWaiter.reject(new RelayClosedError(reason || "COUNT closed", this.url));
|
|
1185
1646
|
return;
|
|
1186
1647
|
}
|
|
1187
|
-
const sub = this.#
|
|
1648
|
+
const sub = this.#subscriptions.get(subId);
|
|
1188
1649
|
if (!sub) return;
|
|
1189
|
-
if (isAuthRequired(reason) && this.#authSigner &&
|
|
1190
|
-
sub
|
|
1191
|
-
this.#authThenResubscribe(sub, reason);
|
|
1650
|
+
if (isAuthRequired(reason) && this.#authSigner && sub.beginAuthRetry()) {
|
|
1651
|
+
this.#subscriptions.authRetry(sub, reason, this.#ensureAuthed);
|
|
1192
1652
|
return;
|
|
1193
1653
|
}
|
|
1194
|
-
|
|
1654
|
+
this.#subscriptions.drop(sub, reason);
|
|
1195
1655
|
break;
|
|
1196
1656
|
}
|
|
1197
1657
|
case "OK": {
|
|
1198
1658
|
const [, eventId, ok, message] = msg;
|
|
1199
|
-
const waiter = this.#
|
|
1659
|
+
const waiter = this.#ok.handleOk(eventId, ok, message, (w) => !ok && isAuthRequired(message) && w.event !== void 0 && !w.authRetried && this.#authSigner !== void 0);
|
|
1200
1660
|
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
|
|
1661
|
+
waiter.authRetried = true;
|
|
1662
|
+
this.#ok.authRetry(waiter, message, {
|
|
1663
|
+
ensureAuthed: this.#ensureAuthed,
|
|
1664
|
+
isOpen: () => this.#isOpen(),
|
|
1665
|
+
send: (event) => this.#send(["EVENT", event])
|
|
1211
1666
|
});
|
|
1212
|
-
|
|
1667
|
+
return;
|
|
1213
1668
|
}
|
|
1214
1669
|
case "COUNT": {
|
|
1215
1670
|
const [, countId, payload] = msg;
|
|
1216
|
-
const waiter = this.#
|
|
1671
|
+
const waiter = this.#countRequests.get(countId);
|
|
1217
1672
|
if (!waiter) return;
|
|
1218
1673
|
waiter.resolve(payload);
|
|
1219
1674
|
break;
|
|
1220
1675
|
}
|
|
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);
|
|
1676
|
+
case "NEG-MSG":
|
|
1677
|
+
case "NEG-ERR":
|
|
1678
|
+
dispatchNegMessage(this.#neg, msg);
|
|
1233
1679
|
break;
|
|
1234
|
-
}
|
|
1235
1680
|
case "NOTICE": {
|
|
1236
1681
|
const [, notice] = msg;
|
|
1237
|
-
this.#emit("notice", notice);
|
|
1682
|
+
this.#events.emit("notice", notice);
|
|
1238
1683
|
break;
|
|
1239
1684
|
}
|
|
1240
1685
|
case "AUTH": {
|
|
1241
1686
|
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);
|
|
1687
|
+
this.#authState.handleChallenge(authChallenge);
|
|
1688
|
+
this.#events.emit("auth", authChallenge);
|
|
1250
1689
|
break;
|
|
1251
1690
|
}
|
|
1252
1691
|
}
|
|
@@ -1256,19 +1695,18 @@ var Relay = class Relay {
|
|
|
1256
1695
|
if (filters.length === 0) throw new MessageError("REQ requires at least one filter");
|
|
1257
1696
|
if (!this.#isOpen() && !this.#enableReconnect) throw new RelayClosedError("not connected", this.url);
|
|
1258
1697
|
const canonical = canonicalizeFilters(filters);
|
|
1259
|
-
|
|
1260
|
-
return subscribeLive(this.#live, canonical, opts);
|
|
1698
|
+
return this.#subscriptions.open(canonical, opts);
|
|
1261
1699
|
}
|
|
1262
1700
|
#acceptEvent(event) {
|
|
1263
1701
|
if (this.#verifyDead) return false;
|
|
1264
1702
|
try {
|
|
1265
1703
|
const ok = this.#verify(event);
|
|
1266
|
-
if (!ok) this.#emit("invalidevent", void 0);
|
|
1704
|
+
if (!ok) this.#events.emit("invalidevent", void 0);
|
|
1267
1705
|
return ok;
|
|
1268
1706
|
} catch (error) {
|
|
1269
1707
|
if (error instanceof WasmPoisonedError) {
|
|
1270
1708
|
this.#verifyDead = true;
|
|
1271
|
-
this.#emit("notice", "wasm-poisoned: instance aborted");
|
|
1709
|
+
this.#events.emit("notice", "wasm-poisoned: instance aborted");
|
|
1272
1710
|
return false;
|
|
1273
1711
|
}
|
|
1274
1712
|
throw error;
|
|
@@ -1299,35 +1737,16 @@ var Relay = class Relay {
|
|
|
1299
1737
|
*/
|
|
1300
1738
|
async publish(event, opts) {
|
|
1301
1739
|
if (!this.#isOpen()) throw new RelayClosedError("not connected", this.url);
|
|
1302
|
-
const existing = this.#
|
|
1303
|
-
if (existing
|
|
1740
|
+
const existing = this.#ok.inFlight(event.id);
|
|
1741
|
+
if (existing !== void 0) return existing;
|
|
1304
1742
|
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
|
-
}
|
|
1743
|
+
return this.#ok.track(event.id, {
|
|
1744
|
+
timeoutMs,
|
|
1745
|
+
timeoutError: () => new RelayTimeoutError("publish timed out", this.url),
|
|
1746
|
+
sendError: () => new RelayPublishError("publish failed", this.url),
|
|
1747
|
+
event,
|
|
1748
|
+
send: () => this.#send(["EVENT", event])
|
|
1329
1749
|
});
|
|
1330
|
-
return promise;
|
|
1331
1750
|
}
|
|
1332
1751
|
/**
|
|
1333
1752
|
* NIP-45 COUNT: ask the relay how many events match `filters`. Resolves with the COUNT payload
|
|
@@ -1340,105 +1759,22 @@ var Relay = class Relay {
|
|
|
1340
1759
|
const canonical = canonicalizeFilters(filters);
|
|
1341
1760
|
const id = opts?.id === void 0 ? this.nextSubId("count") : createSubscriptionId(opts.id);
|
|
1342
1761
|
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
|
-
}
|
|
1762
|
+
return this.#countRequests.track(id, {
|
|
1763
|
+
timeoutMs,
|
|
1764
|
+
timeoutError: () => new RelayTimeoutError("count timed out", this.url),
|
|
1765
|
+
sendError: () => new RelayPublishError("count failed", this.url),
|
|
1766
|
+
filters: [...canonical],
|
|
1767
|
+
signal: opts?.signal,
|
|
1768
|
+
send: () => this.#send([
|
|
1769
|
+
"COUNT",
|
|
1770
|
+
id,
|
|
1771
|
+
...canonical
|
|
1772
|
+
])
|
|
1380
1773
|
});
|
|
1381
1774
|
}
|
|
1382
1775
|
/** NIP-42 AUTH: sign the current challenge and wait for OK. */
|
|
1383
1776
|
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
|
-
}
|
|
1777
|
+
return this.#authState.auth(sign, opts);
|
|
1442
1778
|
}
|
|
1443
1779
|
/**
|
|
1444
1780
|
* Clear a cached AUTH rejection for the current challenge so a later `auth()` signs again. A
|
|
@@ -1446,133 +1782,8 @@ var Relay = class Relay {
|
|
|
1446
1782
|
* `auth` event is re-fired so the pool's automatic auth can run once more.
|
|
1447
1783
|
*/
|
|
1448
1784
|
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
|
-
}
|
|
1785
|
+
const challenge = this.#authState.resetRejection();
|
|
1786
|
+
if (challenge !== void 0) this.#events.emit("auth", challenge);
|
|
1576
1787
|
}
|
|
1577
1788
|
/**
|
|
1578
1789
|
* NIP-77: run Negentropy reconciliation against this relay. Returns the local-only (`have`) and
|
|
@@ -1584,32 +1795,17 @@ var Relay = class Relay {
|
|
|
1584
1795
|
async negReconcile(filter, storage, opts) {
|
|
1585
1796
|
if (!this.#isOpen()) throw new RelayClosedError("not connected", this.url);
|
|
1586
1797
|
throwIfAborted(opts?.signal);
|
|
1587
|
-
const canonical = canonicalizeFilter(filter);
|
|
1588
1798
|
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
|
-
}
|
|
1799
|
+
return runTrackedNegSession({
|
|
1800
|
+
sessions: this.#neg,
|
|
1801
|
+
id,
|
|
1802
|
+
storage,
|
|
1803
|
+
filter: canonicalizeFilter(filter),
|
|
1804
|
+
timeoutMs: opts?.timeoutMs ?? this.#publishTimeoutMs,
|
|
1805
|
+
signal: opts?.signal,
|
|
1806
|
+
send: (message) => this.#send(message),
|
|
1807
|
+
url: this.url
|
|
1808
|
+
});
|
|
1613
1809
|
}
|
|
1614
1810
|
/** Generate a unique subscription id for this relay instance. */
|
|
1615
1811
|
nextSubId(prefix = "sub") {
|
|
@@ -1620,4 +1816,4 @@ var Relay = class Relay {
|
|
|
1620
1816
|
//#endregion
|
|
1621
1817
|
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
1818
|
|
|
1623
|
-
//# sourceMappingURL=relay-
|
|
1819
|
+
//# sourceMappingURL=relay-__QtO33h.mjs.map
|