@nostrify/nostrify 0.49.2 → 0.50.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,32 +1,32 @@
1
1
 
2
- > @nostrify/nostrify@0.49.1 build /home/alex/Projects/nostrify/packages/nostrify
2
+ > @nostrify/nostrify@0.50.1 build /home/alex/Projects/nostrify/packages/nostrify
3
3
  > npx tsc -p tsconfig.json && node ../../esbuild.config.js --package ./
4
4
 
5
5
  npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
6
6
  npm warn Unknown env config "_jsr-registry". This will stop working in the next major version of npm.
7
7
  Building with esbuild...
8
8
 
9
- dist/NRelay1.js 8.9kb
10
- dist/NSchema.js 5.7kb
11
- dist/NPool.js 5.4kb
12
- dist/test/TestRelayServer.js 4.4kb
13
- dist/NSet.js 4.1kb
14
- dist/NConnectSigner.js 3.9kb
15
- dist/ln/LNURL.js 3.3kb
16
- dist/NIP98.js 2.7kb
17
- dist/NBrowserSigner.js 2.6kb
18
- dist/uploaders/BlossomUploader.js 1.9kb
19
- dist/uploaders/NostrBuildUploader.js 1.9kb
20
- dist/test/MockRelay.js 1.4kb
21
- dist/BunkerURI.js 1.4kb
22
- dist/NKinds.js 1.1kb
23
- dist/NIP05.js 1.1kb
24
- dist/NSecSigner.js 1.1kb
25
- dist/utils/Machina.js 925b
26
- dist/NCache.js 828b
27
- dist/mod.js 815b
28
- dist/test/mod.js 701b
9
+ dist/NRelay1.js 10.7kb
10
+ dist/NSchema.js 8.2kb
11
+ dist/NPool.js 5.5kb
12
+ dist/test/TestRelayServer.js 4.4kb
13
+ dist/NSet.js 4.1kb
14
+ dist/NConnectSigner.js 3.9kb
15
+ dist/ln/LNURL.js 3.3kb
16
+ dist/NIP98.js 2.7kb
17
+ dist/NBrowserSigner.js 2.6kb
18
+ dist/uploaders/BlossomUploader.js 1.9kb
19
+ dist/uploaders/NostrBuildUploader.js 1.9kb
20
+ dist/test/MockRelay.js 1.4kb
21
+ dist/BunkerURI.js 1.4kb
22
+ dist/NKinds.js 1.1kb
23
+ dist/NIP05.js 1.1kb
24
+ dist/NSecSigner.js 1.1kb
25
+ dist/utils/Machina.js 925b
26
+ dist/NCache.js 828b
27
+ dist/mod.js 815b
28
+ dist/test/mod.js 701b
29
29
  ...and 11 more output files...
30
30
 
31
- ⚡ Done in 15ms
31
+ ⚡ Done in 17ms
32
32
  Done!
@@ -1,109 +1,110 @@
1
1
 
2
2
 
3
- > @nostrify/nostrify@0.49.1 test /home/alex/Projects/nostrify/packages/nostrify
3
+ > @nostrify/nostrify@0.50.0 test /home/alex/Projects/nostrify/packages/nostrify
4
4
  > node --test "**/*.test.ts"
5
5
 
6
- ✔ BunkerURI (2.855929ms)
7
- ✔ BunkerURI.fromJSON (0.647302ms)
8
- ✔ NBrowserSigner - without extension (5.614283ms)
9
- ✔ NBrowserSigner - with extension polyfill (162.64428ms)
10
- ✔ NBrowserSigner.nip44 - with extension polyfill (35.751206ms)
11
- ✔ NBrowserSigner.nip04 - with extension polyfill (24.897688ms)
12
- ✔ NBrowserSigner.getRelays - with extension polyfill (1.639964ms)
13
- ✔ NBrowserSigner - missing nip44 support (0.681486ms)
14
- ✔ NBrowserSigner - missing nip04 support (0.334181ms)
15
- ✔ NBrowserSigner - feature detection (2.345656ms)
16
- ✔ NCache (4.846063ms)
17
- ✔ NConnectSigner.signEvent with nip04 encryption (284.998411ms)
18
- ✔ NConnectSigner.signEvent with nip44 encryption (79.574293ms)
19
- ✔ NIP05.lookup (94.266524ms)
20
- ✔ NIP05.lookup with invalid values but valid profile pointer (6.109077ms)
21
- ✔ NIP05.lookup with invalid document (7.362813ms)
22
- ✔ NIP50.parseInput (6.692668ms)
23
- ✔ NIP50.parseInput with negated token (0.459908ms)
24
- ✔ NIP98.template (7.800118ms)
25
- ✔ NIP98.template with payload (57.504461ms)
26
- ✔ NIP98.verify (167.402989ms)
27
- ✔ NIP98.verify fails with missing header (1.462579ms)
28
- ✔ NIP98.verify fails with missing token (1.026357ms)
29
- ✔ NIP98.verify fails with invalid token (1.318398ms)
30
- ✔ NIP98.verify fails with invalid event (15.517318ms)
31
- ✔ NIP98.verify fails with wrong event kind (15.461533ms)
32
- ✔ NIP98.verify fails with wrong request URL (12.774894ms)
33
- ✔ NIP98.verify fails with wrong request method (15.755518ms)
34
- ✔ NIP98.verify fails with expired event (11.549622ms)
35
- ✔ NIP98.verify fails with invalid payload (25.108065ms)
36
- ✔ NIP98Client.fetch - basic GET request (152.34065ms)
37
- ✔ NIP98Client.fetch - POST request with body (48.046766ms)
38
- ✔ NIP98Client.fetch - with Request object input (22.539248ms)
39
- ✔ NIP98Client.fetch - with URL object input (14.907818ms)
40
- ✔ NIP98Client.fetch - uses default fetch when not provided (11.87691ms)
41
- ✔ NIP98Client.fetch - preserves existing headers (16.091842ms)
42
- ✔ NIP98Client.fetch - event can be verified with NIP98.verify (20.434596ms)
43
- ✔ NIP98Client.fetch - handles different HTTP methods (54.082414ms)
44
- ✔ NKinds (5.032997ms)
45
- ✔ NPool.query (378.702333ms)
46
- ✔ NPool.req (77.556426ms)
47
- ✔ NPool.event (32.572729ms)
48
- ✔ NPool.query with eoseTimeout (559.204759ms)
49
- ✔ NPool.query with eoseTimeout disabled (519.68897ms)
50
- ✔ NRelay1.query (226.319484ms)
51
- ✔ NRelay1.query mismatched filter (27.633319ms)
52
- ✔ NRelay1.req (66.241496ms)
53
- ✔ NRelay1.event (12.452346ms)
54
- ﹣ NRelay1 backoff (0.21217ms) # SKIP
6
+ ✔ BunkerURI (2.746358ms)
7
+ ✔ BunkerURI.fromJSON (0.592066ms)
8
+ ✔ NBrowserSigner - without extension (2.480267ms)
9
+ ✔ NBrowserSigner - with extension polyfill (166.202262ms)
10
+ ✔ NBrowserSigner.nip44 - with extension polyfill (48.982883ms)
11
+ ✔ NBrowserSigner.nip04 - with extension polyfill (24.263617ms)
12
+ ✔ NBrowserSigner.getRelays - with extension polyfill (1.466605ms)
13
+ ✔ NBrowserSigner - missing nip44 support (1.647116ms)
14
+ ✔ NBrowserSigner - missing nip04 support (0.388212ms)
15
+ ✔ NBrowserSigner - feature detection (1.960176ms)
16
+ ✔ NCache (11.489503ms)
17
+ ✔ NConnectSigner.signEvent with nip04 encryption (236.48745ms)
18
+ ✔ NConnectSigner.signEvent with nip44 encryption (72.89594ms)
19
+ ✔ NIP05.lookup (83.374316ms)
20
+ ✔ NIP05.lookup with invalid values but valid profile pointer (12.756762ms)
21
+ ✔ NIP05.lookup with invalid document (20.462881ms)
22
+ ✔ NIP50.parseInput (7.233418ms)
23
+ ✔ NIP50.parseInput with negated token (0.507317ms)
24
+ ✔ NIP98.template (4.503421ms)
25
+ ✔ NIP98.template with payload (22.610571ms)
26
+ ✔ NIP98.verify (166.641341ms)
27
+ ✔ NIP98.verify fails with missing header (3.102239ms)
28
+ ✔ NIP98.verify fails with missing token (1.647737ms)
29
+ ✔ NIP98.verify fails with invalid token (2.754143ms)
30
+ ✔ NIP98.verify fails with invalid event (20.081863ms)
31
+ ✔ NIP98.verify fails with wrong event kind (25.410189ms)
32
+ ✔ NIP98.verify fails with wrong request URL (19.265203ms)
33
+ ✔ NIP98.verify fails with wrong request method (25.121986ms)
34
+ ✔ NIP98.verify fails with expired event (14.228117ms)
35
+ ✔ NIP98.verify fails with invalid payload (45.486742ms)
36
+ ✔ NIP98Client.fetch - basic GET request (162.816349ms)
37
+ ✔ NIP98Client.fetch - POST request with body (45.826032ms)
38
+ ✔ NIP98Client.fetch - with Request object input (38.387778ms)
39
+ ✔ NIP98Client.fetch - with URL object input (25.195494ms)
40
+ ✔ NIP98Client.fetch - uses default fetch when not provided (11.27616ms)
41
+ ✔ NIP98Client.fetch - preserves existing headers (13.853149ms)
42
+ ✔ NIP98Client.fetch - event can be verified with NIP98.verify (36.586932ms)
43
+ ✔ NIP98Client.fetch - handles different HTTP methods (65.246167ms)
44
+ ✔ NKinds (3.899813ms)
45
+ ✔ NPool.query (480.159465ms)
46
+ ✔ NPool.req (162.806249ms)
47
+ ✔ NPool.event (85.226228ms)
48
+ ✔ NPool.query with eoseTimeout (625.249102ms)
49
+ ✔ NPool.query with eoseTimeout disabled (538.136077ms)
50
+ ✔ NRelay1.query (316.888891ms)
51
+ ✔ NRelay1.query with NIP-50 search preserves relay order (101.77541ms)
52
+ ✔ NRelay1.query mismatched filter (35.660044ms)
53
+ ✔ NRelay1.req (107.715539ms)
54
+ ✔ NRelay1.event (27.486574ms)
55
+ ﹣ NRelay1 backoff (0.35573ms) # SKIP
55
56
  ▶ NRelay1 idleTimeout
56
- ✔ websocket opens (2.256778ms)
57
- ✔ websocket closes after idleTimeout (150.482082ms)
58
- ✔ websocket wakes up during activity (17.379211ms)
59
- ✔ NRelay1 idleTimeout (173.173788ms)
60
- ✔ NRelay1.count rejects when the server sends CLOSED (7.752588ms)
61
- ✔ NRelay1 closes when it receives a binary message (5.304398ms)
62
- ✔ n.id (11.977479ms)
63
- ✔ n.bech32 (4.31372ms)
64
- ✔ n.filter (83.609146ms)
65
- ✔ n.event (39.732538ms)
66
- ✔ n.metadata (67.78575ms)
67
- ✔ NSecSigner (134.833466ms)
68
- ✔ NSecSigner.nip44 (39.725124ms)
69
- ✔ NSet (26.167433ms)
70
- ✔ NSet.add (replaceable) (0.555688ms)
71
- ✔ NSet.add (parameterized) (0.859632ms)
72
- ✔ NSet.add (deletion) (0.659384ms)
73
- ✔ Construct a RelayError from the reason message (2.48014ms)
74
- ✔ Throw a new RelayError if the OK message is false (0.480627ms)
75
- ✔ LNURL.fromString (7.853168ms)
76
- ✔ LNURL.fromLightningAddress (1.565174ms)
77
- ✔ LNURL.toString (1.207389ms)
78
- ✔ LNURL.getDetails (61.890516ms)
79
- ✔ LNURL.getInvoice (12.594574ms)
80
- ✔ ErrorRelay (89.704386ms)
81
- ✔ MockRelay (5.643569ms)
82
- ✔ BlossomUploader.upload (685.760126ms)
83
- ✖ NostrBuildUploader.upload (25.389164ms)
84
- ✔ CircularSet (2.759166ms)
85
- ✔ push, iterate, & close (103.744176ms)
86
- ✔ close & reopen (0.695512ms)
87
- ✔ aborts with signal (51.384223ms)
88
- ✔ already aborted signal in constructor (0.622785ms)
89
- ✔ push after abort (0.654174ms)
90
- ✔ multiple messages in queue (0.316858ms)
91
- ✔ N64 (18.170805ms)
92
- ✔ N64.encodeEvent (0.414843ms)
93
- ✔ N64.decodeEvent (4.505341ms)
94
- ℹ tests 87
57
+ ✔ websocket opens (4.890911ms)
58
+ ✔ websocket closes after idleTimeout (150.939876ms)
59
+ ✔ websocket wakes up during activity (31.703544ms)
60
+ ✔ NRelay1 idleTimeout (193.2402ms)
61
+ ✔ NRelay1.count rejects when the server sends CLOSED (21.977147ms)
62
+ ✔ NRelay1 closes when it receives a binary message (9.325142ms)
63
+ ✔ n.id (19.213084ms)
64
+ ✔ n.bech32 (3.798672ms)
65
+ ✔ n.filter (47.81926ms)
66
+ ✔ n.event (34.382045ms)
67
+ ✔ n.metadata (68.049272ms)
68
+ ✔ NSecSigner (110.350227ms)
69
+ ✔ NSecSigner.nip44 (32.295571ms)
70
+ ✔ NSet (42.044732ms)
71
+ ✔ NSet.add (replaceable) (0.57253ms)
72
+ ✔ NSet.add (parameterized) (1.000306ms)
73
+ ✔ NSet.add (deletion) (0.450089ms)
74
+ ✔ Construct a RelayError from the reason message (2.143422ms)
75
+ ✔ Throw a new RelayError if the OK message is false (0.52506ms)
76
+ ✔ LNURL.fromString (7.847636ms)
77
+ ✔ LNURL.fromLightningAddress (1.686861ms)
78
+ ✔ LNURL.toString (1.142133ms)
79
+ ✔ LNURL.getDetails (86.164146ms)
80
+ ✔ LNURL.getInvoice (13.898675ms)
81
+ ✔ ErrorRelay (94.163638ms)
82
+ ✔ MockRelay (3.052615ms)
83
+ ✔ BlossomUploader.upload (733.428376ms)
84
+ ✖ NostrBuildUploader.upload (33.449327ms)
85
+ ✔ CircularSet (2.438347ms)
86
+ ✔ push, iterate, & close (107.313622ms)
87
+ ✔ close & reopen (0.74796ms)
88
+ ✔ aborts with signal (52.688019ms)
89
+ ✔ already aborted signal in constructor (0.717774ms)
90
+ ✔ push after abort (3.810805ms)
91
+ ✔ multiple messages in queue (0.582538ms)
92
+ ✔ N64 (17.530693ms)
93
+ ✔ N64.encodeEvent (0.432495ms)
94
+ ✔ N64.decodeEvent (4.268328ms)
95
+ ℹ tests 88
95
96
  ℹ suites 0
96
- ℹ pass 85
97
+ ℹ pass 86
97
98
  ℹ fail 1
98
99
  ℹ cancelled 0
99
100
  ℹ skipped 1
100
101
  ℹ todo 0
101
- ℹ duration_ms 3425.548766
102
+ ℹ duration_ms 3673.242963
102
103
 
103
104
  ✖ failing tests:
104
105
 
105
106
  test at uploaders/NostrBuildUploader.test.ts:12:7
106
- ✖ NostrBuildUploader.upload (25.389164ms)
107
+ ✖ NostrBuildUploader.upload (33.449327ms)
107
108
  Error: nostr.build with default uploader requires a secret key to be configured
108
109
  at TestContext.<anonymous> (file:///home/alex/Projects/nostrify/packages/nostrify/uploaders/NostrBuildUploader.test.ts:25:13)
109
110
   at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
@@ -1,5 +1,5 @@
1
1
 
2
- > @nostrify/nostrify@0.49.1 typecheck /home/alex/Projects/nostrify/packages/nostrify
2
+ > @nostrify/nostrify@0.50.1 typecheck /home/alex/Projects/nostrify/packages/nostrify
3
3
  > npx tsc --noEmit
4
4
 
5
5
  npm warn Unknown env config "verify-deps-before-run". This will stop working in the next major version of npm.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.50.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix eoseTimeout to only apply to `query()`, not `req()`. Previously, the EOSE timeout would abort streaming subscriptions via `req()` after the first relay sent EOSE, preventing callers from receiving events past EOSE.
8
+
9
+ ## 0.50.0
10
+
11
+ ### Minor Changes
12
+
13
+ - Add NIP-11 relay information document support to NRelay1 with `getRelayInfo()` method and `NSchema.relayInfo()` validation schema. Fix NRelay1.query to preserve relay-provided ordering for NIP-50 search filters instead of sorting by created_at.
14
+
3
15
  ## 0.49.2
4
16
 
5
17
  ### Patch Changes
package/NPool.ts CHANGED
@@ -102,7 +102,7 @@ export class NPool<T extends NRelay = NRelay> implements NRelay {
102
102
  */
103
103
  async *req(
104
104
  filters: NostrFilter[],
105
- opts?: { signal?: AbortSignal; relays?: string[] },
105
+ opts?: { signal?: AbortSignal; relays?: string[]; eoseTimeout?: number },
106
106
  ): AsyncIterable<NostrRelayEVENT | NostrRelayEOSE | NostrRelayCLOSED> {
107
107
  const controller = new AbortController();
108
108
  const signal = opts?.signal ? AbortSignal.any([opts.signal, controller.signal]) : controller.signal;
@@ -123,7 +123,7 @@ export class NPool<T extends NRelay = NRelay> implements NRelay {
123
123
  const closes = new Set<string>();
124
124
  const events = new CircularSet<string>(1000);
125
125
 
126
- const eoseTimeout = this.opts.eoseTimeout ?? 1000;
126
+ const eoseTimeout = opts?.eoseTimeout;
127
127
  let timeoutId: ReturnType<typeof setTimeout> | undefined;
128
128
 
129
129
  const relayPromises: Promise<void>[] = [];
@@ -136,7 +136,7 @@ export class NPool<T extends NRelay = NRelay> implements NRelay {
136
136
  if (msg[0] === 'EOSE') {
137
137
  eoses.add(url);
138
138
  // Start timeout after first EOSE if timeout is enabled
139
- if (eoses.size === 1 && eoseTimeout > 0 && timeoutId === undefined) {
139
+ if (eoses.size === 1 && eoseTimeout !== undefined && eoseTimeout > 0 && timeoutId === undefined) {
140
140
  timeoutId = setTimeout(() => {
141
141
  controller.abort();
142
142
  }, eoseTimeout);
@@ -226,8 +226,10 @@ export class NPool<T extends NRelay = NRelay> implements NRelay {
226
226
  );
227
227
  if (limit === 0) return [];
228
228
 
229
+ const eoseTimeout = this.opts.eoseTimeout ?? 1000;
230
+
229
231
  try {
230
- for await (const msg of this.req(filters, opts)) {
232
+ for await (const msg of this.req(filters, { ...opts, eoseTimeout })) {
231
233
  if (msg[0] === 'EOSE') break;
232
234
  if (msg[0] === 'EVENT') events.add(msg[2]);
233
235
  if (msg[0] === 'CLOSED') break;
package/NRelay1.test.ts CHANGED
@@ -36,6 +36,38 @@ await test("NRelay1.query", async () => {
36
36
  clearTimeout(tid);
37
37
  });
38
38
 
39
+ await test("NRelay1.query with NIP-50 search preserves relay order", async () => {
40
+ // Create events with different timestamps. The relay will send them
41
+ // in "relevance" order (oldest first), not chronological order.
42
+ const sk = generateSecretKey();
43
+ const oldest = genEvent({ kind: 1, content: "most relevant", created_at: 1000 }, sk);
44
+ const middle = genEvent({ kind: 1, content: "somewhat relevant", created_at: 2000 }, sk);
45
+ const newest = genEvent({ kind: 1, content: "least relevant", created_at: 3000 }, sk);
46
+
47
+ // Relay sends in relevance order: oldest, middle, newest.
48
+ // Without the fix, NSet would sort them newest-first by created_at.
49
+ await using server = await TestRelayServer.create({
50
+ handleMessage(socket, msg) {
51
+ if (msg[0] === "REQ") {
52
+ const [, subId] = msg;
53
+ socket.send(JSON.stringify(["EVENT", subId, oldest]));
54
+ socket.send(JSON.stringify(["EVENT", subId, middle]));
55
+ socket.send(JSON.stringify(["EVENT", subId, newest]));
56
+ socket.send(JSON.stringify(["EOSE", subId]));
57
+ }
58
+ },
59
+ });
60
+
61
+ await using relay = new NRelay1(server.url);
62
+ const events = await relay.query([{ kinds: [1], search: "relevant" }]);
63
+
64
+ deepStrictEqual(events.length, 3);
65
+ // Results should preserve relay order (relevance), not be sorted by created_at.
66
+ deepStrictEqual(events[0].id, oldest.id);
67
+ deepStrictEqual(events[1].id, middle.id);
68
+ deepStrictEqual(events[2].id, newest.id);
69
+ });
70
+
39
71
  await test("NRelay1.query mismatched filter", async () => {
40
72
  await using server = await TestRelayServer.create({
41
73
  handleMessage(socket, msg) {
package/NRelay1.ts CHANGED
@@ -10,6 +10,7 @@ import type {
10
10
  NostrRelayMsg,
11
11
  NostrRelayNOTICE,
12
12
  NostrRelayOK,
13
+ NostrRelayInfo,
13
14
  NRelay,
14
15
  } from '@nostrify/types';
15
16
  import { getFilterLimit, matchFilters, verifyEvent as _verifyEvent } from 'nostr-tools';
@@ -28,7 +29,7 @@ type EventMap = {
28
29
  notice: NostrRelayNOTICE;
29
30
  };
30
31
 
31
- /** Options used for constructing an `NRelay1` instance. */
32
+ /** Options used for constructing an `NRelay1` instance. */
32
33
  export interface NRelay1Opts {
33
34
  /** Respond to `AUTH` challenges by producing a signed kind `22242` event. */
34
35
  auth?(challenge: string): Promise<NostrEvent>;
@@ -40,6 +41,8 @@ export interface NRelay1Opts {
40
41
  verifyEvent?(event: NostrEvent): boolean;
41
42
  /** Logger callback. */
42
43
  log?(log: NRelay1Log): void;
44
+ /** Custom fetch function for retrieving NIP-11 relay information. Default: `globalThis.fetch`. */
45
+ fetch?: typeof globalThis.fetch;
43
46
  }
44
47
 
45
48
  export interface NRelay1Log {
@@ -58,6 +61,7 @@ export class NRelay1 implements NRelay {
58
61
  private controller = new AbortController();
59
62
  private url: string;
60
63
  private opts: NRelay1Opts;
64
+ private relayInfoPromise?: Promise<NostrRelayInfo | undefined>;
61
65
 
62
66
  private ee = new EventTarget();
63
67
 
@@ -76,6 +80,69 @@ export class NRelay1 implements NRelay {
76
80
  this.maybeStartIdleTimer();
77
81
  }
78
82
 
83
+ /** Fetch the NIP-11 relay information document. */
84
+ private async fetchRelayInfo(opts?: { signal?: AbortSignal }): Promise<NostrRelayInfo | undefined> {
85
+ try {
86
+ const { fetch: fetchFn = globalThis.fetch } = this.opts;
87
+ const { signal } = opts || {};
88
+
89
+ const httpUrl = this.url.replace(/^wss?:\/\//, (match) =>
90
+ match === 'ws://' ? 'http://' : 'https://'
91
+ );
92
+
93
+ const response = await fetchFn(httpUrl, {
94
+ headers: { Accept: 'application/nostr+json' },
95
+ signal,
96
+ });
97
+
98
+ if (!response.ok) {
99
+ this.log({
100
+ level: 'warn',
101
+ ns: 'relay.nip11',
102
+ status: response.status,
103
+ message: 'Failed to fetch relay info',
104
+ });
105
+ return undefined;
106
+ }
107
+
108
+ const data = await response.json();
109
+ const result = n.relayInfo().safeParse(data);
110
+
111
+ if (!result.success) {
112
+ this.log({
113
+ level: 'warn',
114
+ ns: 'relay.nip11',
115
+ error: result.error,
116
+ message: 'Invalid relay info format',
117
+ });
118
+ return undefined;
119
+ }
120
+
121
+ this.log({
122
+ level: 'debug',
123
+ ns: 'relay.nip11',
124
+ message: 'Successfully fetched relay info',
125
+ });
126
+ return result.data as NostrRelayInfo;
127
+ } catch (error) {
128
+ this.log({
129
+ level: 'warn',
130
+ ns: 'relay.nip11',
131
+ error: error instanceof Error ? error : new Error(String(error)),
132
+ message: 'Error fetching relay info',
133
+ });
134
+ return undefined;
135
+ }
136
+ }
137
+
138
+ /** Get the NIP-11 relay information document. Fetches on first call and caches the result. */
139
+ async getRelayInfo(opts?: { signal?: AbortSignal }): Promise<NostrRelayInfo | undefined> {
140
+ if (!this.relayInfoPromise) {
141
+ this.relayInfoPromise = this.fetchRelayInfo(opts);
142
+ }
143
+ return this.relayInfoPromise;
144
+ }
145
+
79
146
  /** Create (and open) a WebSocket connection with automatic reconnect. */
80
147
  private createSocket(): Websocket {
81
148
  const { backoff = new ExponentialBackoff(1000) } = this.opts;
@@ -255,7 +322,8 @@ export class NRelay1 implements NRelay {
255
322
  filters: NostrFilter[],
256
323
  opts?: { signal?: AbortSignal },
257
324
  ): Promise<NostrEvent[]> {
258
- const events = new NSet();
325
+ const map = new Map<string, NostrEvent>();
326
+ const events = new NSet(map);
259
327
 
260
328
  const limit = filters.reduce(
261
329
  (result, filter) => result + getFilterLimit(filter),
@@ -273,7 +341,12 @@ export class NRelay1 implements NRelay {
273
341
  }
274
342
  }
275
343
 
276
- return [...events];
344
+ // Don't sort results of search filters.
345
+ if (filters.some((filter) => typeof filter.search === 'string')) {
346
+ return [...map.values()];
347
+ } else {
348
+ return [...events];
349
+ }
277
350
  }
278
351
 
279
352
  async event(
package/NSchema.ts CHANGED
@@ -217,6 +217,51 @@ class NSchema {
217
217
  });
218
218
  }
219
219
 
220
+ /** NIP-11 Relay Information Document schema. */
221
+ static relayInfo() {
222
+ return z.looseObject({
223
+ name: z.string().optional().catch(undefined),
224
+ description: z.string().optional().catch(undefined),
225
+ pubkey: NSchema.id().optional().catch(undefined),
226
+ contact: z.string().optional().catch(undefined),
227
+ supported_nips: z.number().int().nonnegative().array().optional().catch(undefined),
228
+ software: z.string().optional().catch(undefined),
229
+ version: z.string().optional().catch(undefined),
230
+ limitation: z.looseObject({
231
+ max_message_length: z.number().int().nonnegative().optional().catch(undefined),
232
+ max_subscriptions: z.number().int().nonnegative().optional().catch(undefined),
233
+ max_filters: z.number().int().nonnegative().optional().catch(undefined),
234
+ max_limit: z.number().int().nonnegative().optional().catch(undefined),
235
+ max_subid_length: z.number().int().nonnegative().optional().catch(undefined),
236
+ max_event_tags: z.number().int().nonnegative().optional().catch(undefined),
237
+ max_content_length: z.number().int().nonnegative().optional().catch(undefined),
238
+ min_pow_difficulty: z.number().int().nonnegative().optional().catch(undefined),
239
+ auth_required: z.boolean().optional().catch(undefined),
240
+ payment_required: z.boolean().optional().catch(undefined),
241
+ restricted_writes: z.boolean().optional().catch(undefined),
242
+ created_at_lower_limit: z.number().int().nonnegative().optional().catch(undefined),
243
+ created_at_upper_limit: z.number().int().nonnegative().optional().catch(undefined),
244
+ }).optional().catch(undefined),
245
+ retention: z.array(z.object({
246
+ time: z.number().int().nullable(),
247
+ count: z.number().int().nonnegative().optional(),
248
+ kinds: z.number().int().nonnegative().array().optional(),
249
+ })).optional().catch(undefined),
250
+ relay_countries: z.string().array().optional().catch(undefined),
251
+ language_tags: z.string().array().optional().catch(undefined),
252
+ tags: z.string().array().optional().catch(undefined),
253
+ posting_policy: z.string().optional().catch(undefined),
254
+ payments_url: z.string().optional().catch(undefined),
255
+ fees: z.record(z.string(), z.array(z.object({
256
+ amount: z.number(),
257
+ unit: z.string(),
258
+ period: z.number().int().nonnegative().optional(),
259
+ kinds: z.number().int().nonnegative().array().optional(),
260
+ }))).optional().catch(undefined),
261
+ icon: z.string().optional().catch(undefined),
262
+ });
263
+ }
264
+
220
265
  /** NIP-46 request content schema. */
221
266
  static connectRequest() {
222
267
  return z.object({
package/dist/NPool.d.ts CHANGED
@@ -60,6 +60,7 @@ export declare class NPool<T extends NRelay = NRelay> implements NRelay {
60
60
  req(filters: NostrFilter[], opts?: {
61
61
  signal?: AbortSignal;
62
62
  relays?: string[];
63
+ eoseTimeout?: number;
63
64
  }): AsyncIterable<NostrRelayEVENT | NostrRelayEOSE | NostrRelayCLOSED>;
64
65
  /**
65
66
  * Events are sent to relays according to the `eventRouter`.
@@ -1 +1 @@
1
- {"version":3,"file":"NPool.d.ts","sourceRoot":"","sources":["../NPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,MAAM,EACP,MAAM,iBAAiB,CAAC;AAOzB,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,MAAM;IACzC,sDAAsD;IACtD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC;IACrB,8JAA8J;IAC9J,SAAS,CACP,OAAO,EAAE,WAAW,EAAE,GAEpB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GAClC,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IAChD,mJAAmJ;IACnJ,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7D,8JAA8J;IAC9J,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,YAAW,MAAM;IAC7D,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,IAAI,CAAe;gBAEf,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IAI9B,wDAAwD;IACjD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC;IAY5B,6GAA6G;IACtG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;IAQtC,IAAW,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAE1C;IAED;;;;;;;;;OASG;IACI,GAAG,CACR,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,aAAa,CAAC,eAAe,GAAG,cAAc,GAAG,gBAAgB,CAAC;IA6ErE;;;;OAIG;IACG,KAAK,CACT,KAAK,EAAE,UAAU,EACjB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,OAAO,CAAC,IAAI,CAAC;IAahB;;;;;;;;;;;OAWG;IACG,KAAK,CACT,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,OAAO,CAAC,UAAU,EAAE,CAAC;IA4BxB,wCAAwC;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7C"}
1
+ {"version":3,"file":"NPool.d.ts","sourceRoot":"","sources":["../NPool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,MAAM,EACP,MAAM,iBAAiB,CAAC;AAOzB,MAAM,WAAW,SAAS,CAAC,CAAC,SAAS,MAAM;IACzC,sDAAsD;IACtD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAC;IACrB,8JAA8J;IAC9J,SAAS,CACP,OAAO,EAAE,WAAW,EAAE,GAEpB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,GAClC,OAAO,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC;IAChD,mJAAmJ;IACnJ,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC7D,8JAA8J;IAC9J,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,YAAW,MAAM;IAC7D,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,IAAI,CAAe;gBAEf,IAAI,EAAE,SAAS,CAAC,CAAC,CAAC;IAI9B,wDAAwD;IACjD,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC;IAY5B,6GAA6G;IACtG,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC;IAQtC,IAAW,MAAM,IAAI,WAAW,CAAC,MAAM,EAAE,CAAC,CAAC,CAE1C;IAED;;;;;;;;;OASG;IACI,GAAG,CACR,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GACvE,aAAa,CAAC,eAAe,GAAG,cAAc,GAAG,gBAAgB,CAAC;IA6ErE;;;;OAIG;IACG,KAAK,CACT,KAAK,EAAE,UAAU,EACjB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,OAAO,CAAC,IAAI,CAAC;IAahB;;;;;;;;;;;OAWG;IACG,KAAK,CACT,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,GACjD,OAAO,CAAC,UAAU,EAAE,CAAC;IA8BxB,wCAAwC;IAClC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7C"}
package/dist/NPool.js CHANGED
@@ -51,7 +51,7 @@ class NPool {
51
51
  const eoses = /* @__PURE__ */ new Set();
52
52
  const closes = /* @__PURE__ */ new Set();
53
53
  const events = new CircularSet(1e3);
54
- const eoseTimeout = this.opts.eoseTimeout ?? 1e3;
54
+ const eoseTimeout = opts?.eoseTimeout;
55
55
  let timeoutId;
56
56
  const relayPromises = [];
57
57
  for (const [url, filters2] of routes.entries()) {
@@ -61,7 +61,7 @@ class NPool {
61
61
  for await (const msg of relay.req(filters2, { signal })) {
62
62
  if (msg[0] === "EOSE") {
63
63
  eoses.add(url);
64
- if (eoses.size === 1 && eoseTimeout > 0 && timeoutId === void 0) {
64
+ if (eoses.size === 1 && eoseTimeout !== void 0 && eoseTimeout > 0 && timeoutId === void 0) {
65
65
  timeoutId = setTimeout(() => {
66
66
  controller.abort();
67
67
  }, eoseTimeout);
@@ -135,8 +135,9 @@ class NPool {
135
135
  0
136
136
  );
137
137
  if (limit === 0) return [];
138
+ const eoseTimeout = this.opts.eoseTimeout ?? 1e3;
138
139
  try {
139
- for await (const msg of this.req(filters, opts)) {
140
+ for await (const msg of this.req(filters, { ...opts, eoseTimeout })) {
140
141
  if (msg[0] === "EOSE") break;
141
142
  if (msg[0] === "EVENT") events.add(msg[2]);
142
143
  if (msg[0] === "CLOSED") break;
package/dist/NRelay1.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { NostrClientMsg, NostrClientREQ, NostrEvent, NostrFilter, NostrRelayCLOSED, NostrRelayEOSE, NostrRelayEVENT, NostrRelayMsg, NRelay } from '@nostrify/types';
1
+ import type { NostrClientMsg, NostrClientREQ, NostrEvent, NostrFilter, NostrRelayCLOSED, NostrRelayEOSE, NostrRelayEVENT, NostrRelayMsg, NostrRelayInfo, NRelay } from '@nostrify/types';
2
2
  import { Websocket } from 'websocket-ts';
3
3
  import type { Backoff } from 'websocket-ts';
4
4
  /** Options used for constructing an `NRelay1` instance. */
@@ -13,6 +13,8 @@ export interface NRelay1Opts {
13
13
  verifyEvent?(event: NostrEvent): boolean;
14
14
  /** Logger callback. */
15
15
  log?(log: NRelay1Log): void;
16
+ /** Custom fetch function for retrieving NIP-11 relay information. Default: `globalThis.fetch`. */
17
+ fetch?: typeof globalThis.fetch;
16
18
  }
17
19
  export interface NRelay1Log {
18
20
  level: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal' | 'critical';
@@ -30,10 +32,17 @@ export declare class NRelay1 implements NRelay {
30
32
  private controller;
31
33
  private url;
32
34
  private opts;
35
+ private relayInfoPromise?;
33
36
  private ee;
34
37
  get subscriptions(): readonly NostrClientREQ[];
35
38
  private log;
36
39
  constructor(url: string, opts?: NRelay1Opts);
40
+ /** Fetch the NIP-11 relay information document. */
41
+ private fetchRelayInfo;
42
+ /** Get the NIP-11 relay information document. Fetches on first call and caches the result. */
43
+ getRelayInfo(opts?: {
44
+ signal?: AbortSignal;
45
+ }): Promise<NostrRelayInfo | undefined>;
37
46
  /** Create (and open) a WebSocket connection with automatic reconnect. */
38
47
  private createSocket;
39
48
  /** Handle a NIP-01 relay message. */
@@ -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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"}