@nostrify/nostrify 0.50.4 → 0.51.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/NRelay1.ts CHANGED
@@ -13,7 +13,9 @@ import type {
13
13
  NostrRelayInfo,
14
14
  NRelay,
15
15
  } from '@nostrify/types';
16
- import { getFilterLimit, matchFilters, verifyEvent as _verifyEvent } from 'nostr-tools';
16
+ import { matchFilters, verifyEvent as _verifyEvent } from 'nostr-tools';
17
+
18
+ import { getFilterLimit } from './utils/getFilterLimit.ts';
17
19
  import { ArrayQueue, ExponentialBackoff, Websocket, WebsocketBuilder, WebsocketEvent } from 'websocket-ts';
18
20
  import type { Backoff } from 'websocket-ts';
19
21
 
@@ -63,6 +65,15 @@ export class NRelay1 implements NRelay {
63
65
  private opts: NRelay1Opts;
64
66
  private relayInfoPromise?: Promise<NostrRelayInfo | undefined>;
65
67
 
68
+ /** Promise that resolves when the current AUTH flow completes. */
69
+ private authPromise?: Promise<void>;
70
+ /** Set of subscription IDs that have already been retried after auth, to prevent infinite loops. */
71
+ private authRetriedSubs = new Set<string>();
72
+ /** Set of event IDs that have already been retried after auth, to prevent infinite loops. */
73
+ private authRetriedEvents = new Set<string>();
74
+ /** Pending events waiting for AUTH, keyed by event ID. */
75
+ private pendingEvents = new Map<string, NostrEvent>();
76
+
66
77
  private ee = new EventTarget();
67
78
 
68
79
  get subscriptions(): readonly NostrClientREQ[] {
@@ -239,7 +250,12 @@ export class NRelay1 implements NRelay {
239
250
  );
240
251
  break;
241
252
  case 'CLOSED':
253
+ if (auth && msg[2].startsWith('auth-required:') && !this.authRetriedSubs.has(msg[1])) {
254
+ this.retrySubAfterAuth(msg[1]);
255
+ break;
256
+ }
242
257
  this.subs.delete(msg[1]);
258
+ this.authRetriedSubs.delete(msg[1]);
243
259
  this.maybeStartIdleTimer();
244
260
  this.ee.dispatchEvent(
245
261
  new CustomEvent(`sub:${msg[1]}`, { detail: msg }),
@@ -249,6 +265,12 @@ export class NRelay1 implements NRelay {
249
265
  );
250
266
  break;
251
267
  case 'OK':
268
+ if (auth && !msg[2] && msg[3].startsWith('auth-required:') && !this.authRetriedEvents.has(msg[1])) {
269
+ this.retryEventAfterAuth(msg[1]);
270
+ break;
271
+ }
272
+ this.pendingEvents.delete(msg[1]);
273
+ this.authRetriedEvents.delete(msg[1]);
252
274
  this.ee.dispatchEvent(new CustomEvent(`ok:${msg[1]}`, { detail: msg }));
253
275
  break;
254
276
  case 'NOTICE':
@@ -260,9 +282,62 @@ export class NRelay1 implements NRelay {
260
282
  );
261
283
  break;
262
284
  case 'AUTH':
263
- auth?.(msg[1]).then((event) => this.send(['AUTH', event])).catch(
264
- () => {},
265
- );
285
+ if (auth) {
286
+ this.authPromise = this.doAuth(auth, msg[1]);
287
+ }
288
+ }
289
+ }
290
+
291
+ /** Perform NIP-42 authentication and wait for the relay's OK response. */
292
+ private async doAuth(auth: (challenge: string) => Promise<NostrEvent>, challenge: string): Promise<void> {
293
+ try {
294
+ const event = await auth(challenge);
295
+ const result = this.once(`ok:${event.id}`);
296
+ this.send(['AUTH', event]);
297
+ const [, , ok] = await result;
298
+ if (!ok) {
299
+ this.log({ level: 'warn', ns: 'relay.auth', message: 'AUTH failed' });
300
+ }
301
+ } catch {
302
+ // AUTH failed, nothing to do
303
+ }
304
+ }
305
+
306
+ /** Re-send a subscription after AUTH completes. */
307
+ private async retrySubAfterAuth(subscriptionId: string): Promise<void> {
308
+ const req = this.subs.get(subscriptionId);
309
+ if (!req) return;
310
+
311
+ this.authRetriedSubs.add(subscriptionId);
312
+
313
+ try {
314
+ await this.authPromise;
315
+ } catch {
316
+ // AUTH failed — fall through to let the CLOSED propagate
317
+ }
318
+
319
+ // Re-send the original REQ if the subscription is still active.
320
+ if (this.subs.has(subscriptionId)) {
321
+ this.send(req);
322
+ }
323
+ }
324
+
325
+ /** Re-send an event after AUTH completes. */
326
+ private async retryEventAfterAuth(eventId: string): Promise<void> {
327
+ const event = this.pendingEvents.get(eventId);
328
+ if (!event) return;
329
+
330
+ this.authRetriedEvents.add(eventId);
331
+
332
+ try {
333
+ await this.authPromise;
334
+ } catch {
335
+ // AUTH failed — fall through to let the failed OK propagate
336
+ }
337
+
338
+ // Re-send the event if it's still pending.
339
+ if (this.pendingEvents.has(eventId)) {
340
+ this.send(['EVENT', event]);
266
341
  }
267
342
  }
268
343
 
@@ -280,6 +355,8 @@ export class NRelay1 implements NRelay {
280
355
  this.maybeStartIdleTimer();
281
356
  break;
282
357
  case 'EVENT':
358
+ this.pendingEvents.set(msg[1].id, msg[1]);
359
+ return this.socket.send(JSON.stringify(msg));
283
360
  case 'COUNT':
284
361
  return this.socket.send(JSON.stringify(msg));
285
362
  }
package/dist/NIP05.js CHANGED
@@ -2,7 +2,7 @@ import { NSchema as n, z } from "./NSchema.js";
2
2
  class NIP05 {
3
3
  /** NIP-05 value regex. */
4
4
  static regex() {
5
- return /^(?:([\w.+-]+)@)?([\w.-]+)$/;
5
+ return /^(?:([\w.+-]+)@)?((?:[\w-]+\.)+[\w-]+)$/;
6
6
  }
7
7
  /** Nostr pubkey with relays object. */
8
8
  static profilePointerSchema() {
package/dist/NPool.js CHANGED
@@ -1,4 +1,4 @@
1
- import { getFilterLimit } from "nostr-tools";
1
+ import { getFilterLimit } from "./utils/getFilterLimit.js";
2
2
  import { CircularSet } from "./utils/CircularSet.js";
3
3
  import { Machina } from "./utils/Machina.js";
4
4
  import { NSet } from "./NSet.js";
package/dist/NRelay1.d.ts CHANGED
@@ -33,6 +33,14 @@ export declare class NRelay1 implements NRelay {
33
33
  private url;
34
34
  private opts;
35
35
  private relayInfoPromise?;
36
+ /** Promise that resolves when the current AUTH flow completes. */
37
+ private authPromise?;
38
+ /** Set of subscription IDs that have already been retried after auth, to prevent infinite loops. */
39
+ private authRetriedSubs;
40
+ /** Set of event IDs that have already been retried after auth, to prevent infinite loops. */
41
+ private authRetriedEvents;
42
+ /** Pending events waiting for AUTH, keyed by event ID. */
43
+ private pendingEvents;
36
44
  private ee;
37
45
  get subscriptions(): readonly NostrClientREQ[];
38
46
  private log;
@@ -47,6 +55,12 @@ export declare class NRelay1 implements NRelay {
47
55
  private createSocket;
48
56
  /** Handle a NIP-01 relay message. */
49
57
  protected receive(msg: NostrRelayMsg): void;
58
+ /** Perform NIP-42 authentication and wait for the relay's OK response. */
59
+ private doAuth;
60
+ /** Re-send a subscription after AUTH completes. */
61
+ private retrySubAfterAuth;
62
+ /** Re-send an event after AUTH completes. */
63
+ private retryEventAfterAuth;
50
64
  /** Send a NIP-01 client message to the relay. */
51
65
  protected send(msg: NostrClientMsg): void;
52
66
  req(filters: NostrFilter[], opts?: {
@@ -1 +1 @@
1
- {"version":3,"file":"NRelay1.d.ts","sourceRoot":"","sources":["../NRelay1.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"NRelay1.d.ts","sourceRoot":"","sources":["../NRelay1.ts"],"names":[],"mappings":"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"}
package/dist/NRelay1.js CHANGED
@@ -1,4 +1,5 @@
1
- import { getFilterLimit, matchFilters, verifyEvent as _verifyEvent } from "nostr-tools";
1
+ import { matchFilters, verifyEvent as _verifyEvent } from "nostr-tools";
2
+ import { getFilterLimit } from "./utils/getFilterLimit.js";
2
3
  import { ArrayQueue, ExponentialBackoff, WebsocketBuilder, WebsocketEvent } from "websocket-ts";
3
4
  import { Machina } from "./utils/Machina.js";
4
5
  import { NSchema as n } from "./NSchema.js";
@@ -12,6 +13,14 @@ class NRelay1 {
12
13
  url;
13
14
  opts;
14
15
  relayInfoPromise;
16
+ /** Promise that resolves when the current AUTH flow completes. */
17
+ authPromise;
18
+ /** Set of subscription IDs that have already been retried after auth, to prevent infinite loops. */
19
+ authRetriedSubs = /* @__PURE__ */ new Set();
20
+ /** Set of event IDs that have already been retried after auth, to prevent infinite loops. */
21
+ authRetriedEvents = /* @__PURE__ */ new Set();
22
+ /** Pending events waiting for AUTH, keyed by event ID. */
23
+ pendingEvents = /* @__PURE__ */ new Map();
15
24
  ee = new EventTarget();
16
25
  get subscriptions() {
17
26
  return [...this.subs.values()];
@@ -162,7 +171,12 @@ class NRelay1 {
162
171
  );
163
172
  break;
164
173
  case "CLOSED":
174
+ if (auth && msg[2].startsWith("auth-required:") && !this.authRetriedSubs.has(msg[1])) {
175
+ this.retrySubAfterAuth(msg[1]);
176
+ break;
177
+ }
165
178
  this.subs.delete(msg[1]);
179
+ this.authRetriedSubs.delete(msg[1]);
166
180
  this.maybeStartIdleTimer();
167
181
  this.ee.dispatchEvent(
168
182
  new CustomEvent(`sub:${msg[1]}`, { detail: msg })
@@ -172,6 +186,12 @@ class NRelay1 {
172
186
  );
173
187
  break;
174
188
  case "OK":
189
+ if (auth && !msg[2] && msg[3].startsWith("auth-required:") && !this.authRetriedEvents.has(msg[1])) {
190
+ this.retryEventAfterAuth(msg[1]);
191
+ break;
192
+ }
193
+ this.pendingEvents.delete(msg[1]);
194
+ this.authRetriedEvents.delete(msg[1]);
175
195
  this.ee.dispatchEvent(new CustomEvent(`ok:${msg[1]}`, { detail: msg }));
176
196
  break;
177
197
  case "NOTICE":
@@ -183,10 +203,48 @@ class NRelay1 {
183
203
  );
184
204
  break;
185
205
  case "AUTH":
186
- auth?.(msg[1]).then((event) => this.send(["AUTH", event])).catch(
187
- () => {
188
- }
189
- );
206
+ if (auth) {
207
+ this.authPromise = this.doAuth(auth, msg[1]);
208
+ }
209
+ }
210
+ }
211
+ /** Perform NIP-42 authentication and wait for the relay's OK response. */
212
+ async doAuth(auth, challenge) {
213
+ try {
214
+ const event = await auth(challenge);
215
+ const result = this.once(`ok:${event.id}`);
216
+ this.send(["AUTH", event]);
217
+ const [, , ok] = await result;
218
+ if (!ok) {
219
+ this.log({ level: "warn", ns: "relay.auth", message: "AUTH failed" });
220
+ }
221
+ } catch {
222
+ }
223
+ }
224
+ /** Re-send a subscription after AUTH completes. */
225
+ async retrySubAfterAuth(subscriptionId) {
226
+ const req = this.subs.get(subscriptionId);
227
+ if (!req) return;
228
+ this.authRetriedSubs.add(subscriptionId);
229
+ try {
230
+ await this.authPromise;
231
+ } catch {
232
+ }
233
+ if (this.subs.has(subscriptionId)) {
234
+ this.send(req);
235
+ }
236
+ }
237
+ /** Re-send an event after AUTH completes. */
238
+ async retryEventAfterAuth(eventId) {
239
+ const event = this.pendingEvents.get(eventId);
240
+ if (!event) return;
241
+ this.authRetriedEvents.add(eventId);
242
+ try {
243
+ await this.authPromise;
244
+ } catch {
245
+ }
246
+ if (this.pendingEvents.has(eventId)) {
247
+ this.send(["EVENT", event]);
190
248
  }
191
249
  }
192
250
  /** Send a NIP-01 client message to the relay. */
@@ -202,6 +260,8 @@ class NRelay1 {
202
260
  this.maybeStartIdleTimer();
203
261
  break;
204
262
  case "EVENT":
263
+ this.pendingEvents.set(msg[1].id, msg[1]);
264
+ return this.socket.send(JSON.stringify(msg));
205
265
  case "COUNT":
206
266
  return this.socket.send(JSON.stringify(msg));
207
267
  }
@@ -20,6 +20,8 @@ export declare class TestRelayServer {
20
20
  get url(): string;
21
21
  close(): Promise<void>;
22
22
  open(): Promise<void>;
23
+ /** Close all active WebSocket connections without shutting down the server. */
24
+ dropConnections(): void;
23
25
  event(event: NostrEvent): Promise<void>;
24
26
  [Symbol.asyncDispose](): Promise<void>;
25
27
  static create(opts?: TestRelayServerOpts): Promise<TestRelayServer>;
@@ -1 +1 @@
1
- {"version":3,"file":"TestRelayServer.d.ts","sourceRoot":"","sources":["../../test/TestRelayServer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEjF,OAAO,EAAE,SAAS,EAAmB,MAAM,IAAI,CAAC;AAKhD,UAAU,mBAAmB;IAC3B,aAAa,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9E;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,WAAW,CAAsC;IACzD,OAAO,CAAC,KAAK,CAAmB;gBAEpB,IAAI,CAAC,EAAE,mBAAmB;IAOtC,IAAI;IAWJ,OAAO,CAAC,oBAAoB;IAgC5B,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI;YAOnC,aAAa;IA2C3B,IAAI,GAAG,IAAI,MAAM,CAIhB;IAGK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBrB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;WAM/B,MAAM,CAAC,IAAI,CAAC,EAAE,mBAAmB;CAK/C"}
1
+ {"version":3,"file":"TestRelayServer.d.ts","sourceRoot":"","sources":["../../test/TestRelayServer.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEjF,OAAO,EAAE,SAAS,EAAmB,MAAM,IAAI,CAAC;AAKhD,UAAU,mBAAmB;IAC3B,aAAa,CAAC,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CAC9E;AAED,qBAAa,eAAe;IAC1B,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,IAAI,CAAsB;IAClC,OAAO,CAAC,WAAW,CAAwB;IAC3C,OAAO,CAAC,WAAW,CAAsC;IACzD,OAAO,CAAC,KAAK,CAAmB;gBAEpB,IAAI,CAAC,EAAE,mBAAmB;IAOtC,IAAI;IAWJ,OAAO,CAAC,oBAAoB;IAgC5B,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,aAAa,GAAG,IAAI;YAOnC,aAAa;IA2C3B,IAAI,GAAG,IAAI,MAAM,CAIhB;IAGK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAsB5B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiBrB,+EAA+E;IAC/E,eAAe,IAAI,IAAI;IASvB,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAKjC,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;WAM/B,MAAM,CAAC,IAAI,CAAC,EAAE,mBAAmB;CAK/C"}
@@ -129,6 +129,15 @@ class TestRelayServer {
129
129
  }
130
130
  return Promise.resolve();
131
131
  }
132
+ /** Close all active WebSocket connections without shutting down the server. */
133
+ dropConnections() {
134
+ if (!this.inited) throw new Error("TestRelayServer not initialized");
135
+ this.connections.forEach((conn) => {
136
+ if (conn.readyState === WebSocket.OPEN) {
137
+ conn.close();
138
+ }
139
+ });
140
+ }
132
141
  event(event) {
133
142
  if (!this.inited) throw new Error("TestRelayServer not initialized");
134
143
  return this.store.event(event);