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.
- package/.github/workflows/npm-publish.yml +4 -3
- package/.github/workflows/test.yml +4 -3
- package/README.md +36 -1
- package/fakejson.test.js +14 -0
- package/fakejson.ts +15 -0
- package/index.ts +1 -0
- package/justfile +13 -0
- package/lib/nostr.bundle.js +106 -5
- package/lib/nostr.bundle.js.map +3 -3
- package/lib/nostr.cjs.js +106 -6
- package/lib/nostr.cjs.js.map +3 -3
- package/lib/nostr.esm.js +104 -4
- package/lib/nostr.esm.js.map +3 -3
- package/nip05.ts +1 -1
- package/package.json +1 -6
- package/pool.test.js +119 -0
- package/pool.ts +68 -0
- package/relay.ts +16 -9
- package/utils.ts +14 -0
|
@@ -12,9 +12,10 @@ jobs:
|
|
|
12
12
|
- uses: actions/setup-node@v3
|
|
13
13
|
with:
|
|
14
14
|
node-version: 18
|
|
15
|
-
-
|
|
16
|
-
- run:
|
|
17
|
-
- run:
|
|
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 }}
|
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',
|
|
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
package/justfile
ADDED
package/lib/nostr.bundle.js
CHANGED
|
@@ -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 !==
|
|
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
|
|
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
|
|
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(/^[
|
|
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])
|