nostr-tools 1.2.1 → 1.2.4

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.
@@ -12,9 +12,10 @@ jobs:
12
12
  - uses: actions/setup-node@v3
13
13
  with:
14
14
  node-version: 18
15
- - run: yarn --ignore-engines
16
- - run: node build.js
17
- - run: yarn test
15
+ - uses: extractions/setup-just@v1
16
+ - run: just install-dependencies
17
+ - run: just build
18
+ - run: just test
18
19
  - uses: JS-DevTools/npm-publish@v1
19
20
  with:
20
21
  token: ${{ secrets.NPM_TOKEN }}
@@ -11,6 +11,7 @@ jobs:
11
11
  - uses: actions/setup-node@v3
12
12
  with:
13
13
  node-version: 18
14
- - run: yarn --ignore-engines
15
- - run: node build.js
16
- - run: yarn test
14
+ - uses: extractions/setup-just@v1
15
+ - run: just install-dependencies
16
+ - run: just build
17
+ - run: just test
package/README.md CHANGED
@@ -120,6 +120,41 @@ To use this on Node.js you first must install `websocket-polyfill` and import it
120
120
  import 'websocket-polyfill'
121
121
  ```
122
122
 
123
+ ### Interacting with multiple relays
124
+
125
+ ```js
126
+ import {pool} from 'nostr-tools'
127
+
128
+ const pool = new SimplePool()
129
+
130
+ let relays = ['wss://relay.example.com', 'wss://relay.example2.com']
131
+
132
+ relays.forEach(async url => {
133
+ let relay = pool.ensureRelay(url)
134
+ await relay.connect()
135
+ })
136
+
137
+ let relay = pool.ensureRelay('wss://relay.example3.com')
138
+
139
+ let subs = pool.sub([...relays, relay], {
140
+ authors: ['32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245']
141
+ })
142
+
143
+ subs.forEach(sub =>
144
+ sub.on('event', event => {
145
+ // this will only be called once the first time the event is received
146
+ // ...
147
+ })
148
+ )
149
+
150
+ let pubs = pool.publish(newEvent)
151
+ pubs.forEach(pub =>
152
+ pub.on('ok', () => {
153
+ // ...
154
+ })
155
+ )
156
+ ```
157
+
123
158
  ### Querying profile data from a NIP-05 address
124
159
 
125
160
  ```js
@@ -195,7 +230,7 @@ let event = {
195
230
  sendEvent(event)
196
231
 
197
232
  // on the receiver side
198
- sub.on('event', (event) => {
233
+ sub.on('event', event => {
199
234
  let sender = event.tags.find(([k, v]) => k === 'p' && v && v !== '')[1]
200
235
  pk1 === sender
201
236
  let plaintext = await nip04.decrypt(sk2, pk1, event.content)
package/fakejson.test.js CHANGED
@@ -33,3 +33,17 @@ test('match kind', () => {
33
33
  )
34
34
  ).toBeTruthy()
35
35
  })
36
+
37
+ test('match subscription id', () => {
38
+ expect(fj.getSubscriptionId('["EVENT","",{}]')).toEqual('')
39
+ expect(fj.getSubscriptionId('["EVENT","_",{}]')).toEqual('_')
40
+ expect(fj.getSubscriptionId('["EVENT","subname",{}]')).toEqual('subname')
41
+ expect(fj.getSubscriptionId('["EVENT", "kasjbdjkav", {}]')).toEqual(
42
+ 'kasjbdjkav'
43
+ )
44
+ expect(
45
+ fj.getSubscriptionId(
46
+ ' [ \n\n "EVENT" , \n\n "y4d5ow45gfwoiudfÇA VSADLKAN KLDASB[12312535]SFMZSNJKLH" , {}]'
47
+ )
48
+ ).toEqual('y4d5ow45gfwoiudfÇA VSADLKAN KLDASB[12312535]SFMZSNJKLH')
49
+ })
package/fakejson.ts CHANGED
@@ -13,6 +13,21 @@ export function getInt(json: string, field: string): number {
13
13
  return parseInt(sliced.slice(0, end), 10)
14
14
  }
15
15
 
16
+ export function getSubscriptionId(json: string): string | null {
17
+ let idx = json.slice(0, 22).indexOf(`"EVENT"`)
18
+ if (idx === -1) return null
19
+
20
+ let pstart = json.slice(idx + 7 + 1).indexOf(`"`)
21
+ if (pstart === -1) return null
22
+ let start = idx + 7 + 1 + pstart
23
+
24
+ let pend = json.slice(start + 1, 80).indexOf(`"`)
25
+ if (pend === -1) return null
26
+ let end = start + 1 + pend
27
+
28
+ return json.slice(start + 1, end)
29
+ }
30
+
16
31
  export function matchEventId(json: string, id: string): boolean {
17
32
  return id === getHex64(json, 'id')
18
33
  }
package/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from './keys'
2
2
  export * from './relay'
3
3
  export * from './event'
4
4
  export * from './filter'
5
+ export * from './pool'
5
6
 
6
7
  export * as nip04 from './nip04'
7
8
  export * as nip05 from './nip05'
package/justfile ADDED
@@ -0,0 +1,13 @@
1
+ export PATH := "./node_modules/.bin:" + env_var('PATH')
2
+
3
+ install-dependencies:
4
+ yarn --ignore-engines
5
+
6
+ build:
7
+ node build.js
8
+
9
+ test: build
10
+ jest
11
+
12
+ testOnly file: build
13
+ jest {{file}}
@@ -3558,7 +3558,7 @@ zoo`.split("\n");
3558
3558
  return new Uint8Array([(0, sha256_1.sha256)(entropy)[0] >> bitsLeft << bitsLeft]);
3559
3559
  };
3560
3560
  function getCoder(wordlist2) {
3561
- if (!Array.isArray(wordlist2) || wordlist2.length !== 2 ** 11 || typeof wordlist2[0] !== "string")
3561
+ if (!Array.isArray(wordlist2) || wordlist2.length !== 2048 || typeof wordlist2[0] !== "string")
3562
3562
  throw new Error("Worlist: expected array of 2048 strings");
3563
3563
  wordlist2.forEach((i) => {
3564
3564
  if (typeof i !== "string")
@@ -3604,6 +3604,7 @@ zoo`.split("\n");
3604
3604
  var nostr_tools_exports = {};
3605
3605
  __export(nostr_tools_exports, {
3606
3606
  Kind: () => Kind,
3607
+ SimplePool: () => SimplePool,
3607
3608
  fj: () => fakejson_exports,
3608
3609
  generatePrivateKey: () => generatePrivateKey,
3609
3610
  getBlankEvent: () => getBlankEvent,
@@ -5164,12 +5165,24 @@ zoo`.split("\n");
5164
5165
  __export(utils_exports, {
5165
5166
  insertEventIntoAscendingList: () => insertEventIntoAscendingList,
5166
5167
  insertEventIntoDescendingList: () => insertEventIntoDescendingList,
5168
+ normalizeURL: () => normalizeURL,
5167
5169
  utf8Decoder: () => utf8Decoder,
5168
5170
  utf8Encoder: () => utf8Encoder
5169
5171
  });
5170
5172
  init_define_process();
5171
5173
  var utf8Decoder = new TextDecoder("utf-8");
5172
5174
  var utf8Encoder = new TextEncoder();
5175
+ function normalizeURL(url) {
5176
+ let p = new URL(url);
5177
+ p.pathname = p.pathname.replace(/\/+/g, "/");
5178
+ if (p.pathname.endsWith("/"))
5179
+ p.pathname = p.pathname.slice(0, -1);
5180
+ if (p.port === "80" && p.protocol === "ws:" || p.port === "443" && p.protocol === "wss:")
5181
+ p.port = "";
5182
+ p.searchParams.sort();
5183
+ p.hash = "";
5184
+ return p.toString();
5185
+ }
5173
5186
  function insertEventIntoDescendingList(sortedArray, event) {
5174
5187
  let start = 0;
5175
5188
  let end = sortedArray.length - 1;
@@ -5357,6 +5370,7 @@ zoo`.split("\n");
5357
5370
  __export(fakejson_exports, {
5358
5371
  getHex64: () => getHex64,
5359
5372
  getInt: () => getInt,
5373
+ getSubscriptionId: () => getSubscriptionId,
5360
5374
  matchEventId: () => matchEventId,
5361
5375
  matchEventKind: () => matchEventKind,
5362
5376
  matchEventPubkey: () => matchEventPubkey
@@ -5375,6 +5389,20 @@ zoo`.split("\n");
5375
5389
  let end = Math.min(sliced.indexOf(","), sliced.indexOf("}"));
5376
5390
  return parseInt(sliced.slice(0, end), 10);
5377
5391
  }
5392
+ function getSubscriptionId(json) {
5393
+ let idx = json.slice(0, 22).indexOf(`"EVENT"`);
5394
+ if (idx === -1)
5395
+ return null;
5396
+ let pstart = json.slice(idx + 7 + 1).indexOf(`"`);
5397
+ if (pstart === -1)
5398
+ return null;
5399
+ let start = idx + 7 + 1 + pstart;
5400
+ let pend = json.slice(start + 1, 80).indexOf(`"`);
5401
+ if (pend === -1)
5402
+ return null;
5403
+ let end = start + 1 + pend;
5404
+ return json.slice(start + 1, end);
5405
+ }
5378
5406
  function matchEventId(json, id) {
5379
5407
  return id === getHex64(json, "id");
5380
5408
  }
@@ -5386,7 +5414,7 @@ zoo`.split("\n");
5386
5414
  }
5387
5415
 
5388
5416
  // relay.ts
5389
- function relayInit(url, alreadyHaveEvent = () => false) {
5417
+ function relayInit(url) {
5390
5418
  var ws;
5391
5419
  var resolveClose;
5392
5420
  var setOpen;
@@ -5433,8 +5461,14 @@ zoo`.split("\n");
5433
5461
  return;
5434
5462
  }
5435
5463
  var json = incomingMessageQueue.shift();
5436
- if (!json || alreadyHaveEvent(getHex64(json, "id"))) {
5464
+ if (!json)
5437
5465
  return;
5466
+ let subid = getSubscriptionId(json);
5467
+ if (subid) {
5468
+ let { alreadyHaveEvent } = openSubs[subid];
5469
+ if (alreadyHaveEvent && alreadyHaveEvent(getHex64(json, "id"))) {
5470
+ return;
5471
+ }
5438
5472
  }
5439
5473
  try {
5440
5474
  let data = JSON.parse(json);
@@ -5489,18 +5523,21 @@ zoo`.split("\n");
5489
5523
  }
5490
5524
  const sub = (filters, {
5491
5525
  skipVerification = false,
5526
+ alreadyHaveEvent = null,
5492
5527
  id = Math.random().toString().slice(2)
5493
5528
  } = {}) => {
5494
5529
  let subid = id;
5495
5530
  openSubs[subid] = {
5496
5531
  id: subid,
5497
5532
  filters,
5498
- skipVerification
5533
+ skipVerification,
5534
+ alreadyHaveEvent
5499
5535
  };
5500
5536
  trySend(["REQ", subid, ...filters]);
5501
5537
  return {
5502
5538
  sub: (newFilters, newOpts = {}) => sub(newFilters || filters, {
5503
5539
  skipVerification: newOpts.skipVerification || skipVerification,
5540
+ alreadyHaveEvent: newOpts.alreadyHaveEvent || alreadyHaveEvent,
5504
5541
  id: subid
5505
5542
  }),
5506
5543
  unsub: () => {
@@ -5605,6 +5642,70 @@ zoo`.split("\n");
5605
5642
  };
5606
5643
  }
5607
5644
 
5645
+ // pool.ts
5646
+ init_define_process();
5647
+ var SimplePool = class {
5648
+ _conn;
5649
+ constructor(defaultRelays = []) {
5650
+ this._conn = {};
5651
+ defaultRelays.forEach(this.ensureRelay);
5652
+ }
5653
+ ensureRelay(url) {
5654
+ const nm = normalizeURL(url);
5655
+ const existing = this._conn[nm];
5656
+ if (existing)
5657
+ return existing;
5658
+ const relay = relayInit(nm);
5659
+ this._conn[nm] = relay;
5660
+ return relay;
5661
+ }
5662
+ sub(relays, filters, opts) {
5663
+ let _knownIds = /* @__PURE__ */ new Set();
5664
+ let modifiedOpts = opts || {};
5665
+ modifiedOpts.alreadyHaveEvent = (id) => _knownIds.has(id);
5666
+ return relays.map((relay) => {
5667
+ let r = this._conn[relay];
5668
+ if (!r)
5669
+ return badSub();
5670
+ let s = r.sub(filters, modifiedOpts);
5671
+ s.on("event", (event) => _knownIds.add(event.id));
5672
+ return s;
5673
+ });
5674
+ }
5675
+ publish(relays, event) {
5676
+ return relays.map((relay) => {
5677
+ let r = this._conn[relay];
5678
+ if (!r)
5679
+ return badPub(relay);
5680
+ let s = r.publish(event);
5681
+ return s;
5682
+ });
5683
+ }
5684
+ };
5685
+ function badSub() {
5686
+ return {
5687
+ on() {
5688
+ },
5689
+ off() {
5690
+ },
5691
+ sub() {
5692
+ return badSub();
5693
+ },
5694
+ unsub() {
5695
+ }
5696
+ };
5697
+ }
5698
+ function badPub(relay) {
5699
+ return {
5700
+ on(typ, cb) {
5701
+ if (typ === "failed")
5702
+ cb(`relay ${relay} not connected`);
5703
+ },
5704
+ off() {
5705
+ }
5706
+ };
5707
+ }
5708
+
5608
5709
  // nip04.ts
5609
5710
  var nip04_exports = {};
5610
5711
  __export(nip04_exports, {
@@ -6072,7 +6173,7 @@ zoo`.split("\n");
6072
6173
  domain = name;
6073
6174
  name = "_";
6074
6175
  }
6075
- if (!name.match(/^[a-z0-9-_]+$/))
6176
+ if (!name.match(/^[A-Za-z0-9-_]+$/))
6076
6177
  return null;
6077
6178
  let res = await (await _fetch(`https://${domain}/.well-known/nostr.json?name=${name}`)).json();
6078
6179
  if (!res?.names?.[name])