mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-08 16:28:49 +00:00
improvements and fixes on pool.
This commit is contained in:
@@ -134,12 +134,7 @@ const pool = new SimplePool()
|
|||||||
|
|
||||||
let relays = ['wss://relay.example.com', 'wss://relay.example2.com']
|
let relays = ['wss://relay.example.com', 'wss://relay.example2.com']
|
||||||
|
|
||||||
relays.forEach(async url => {
|
let relay = await pool.ensureRelay('wss://relay.example3.com')
|
||||||
let relay = pool.ensureRelay(url)
|
|
||||||
await relay.connect()
|
|
||||||
})
|
|
||||||
|
|
||||||
let relay = pool.ensureRelay('wss://relay.example3.com')
|
|
||||||
|
|
||||||
let subs = pool.sub([...relays, relay], {
|
let subs = pool.sub([...relays, relay], {
|
||||||
authors: ['32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245']
|
authors: ['32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245']
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nostr-tools",
|
"name": "nostr-tools",
|
||||||
"version": "1.3.0",
|
"version": "1.3.1",
|
||||||
"description": "Tools for making a Nostr client.",
|
"description": "Tools for making a Nostr client.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
100
pool.test.js
100
pool.test.js
@@ -19,50 +19,23 @@ let relays = [
|
|||||||
'wss://nostr.zebedee.cloud/'
|
'wss://nostr.zebedee.cloud/'
|
||||||
]
|
]
|
||||||
|
|
||||||
beforeAll(async () => {
|
|
||||||
Promise.all(
|
|
||||||
relays.map(relay => {
|
|
||||||
try {
|
|
||||||
let r = pool.ensureRelay(relay)
|
|
||||||
return r.connect()
|
|
||||||
} catch (err) {
|
|
||||||
/***/
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
relays.forEach(relay => {
|
await pool.close([...relays, 'wss://nostr-relay.untethr.me'])
|
||||||
try {
|
|
||||||
let r = pool.ensureRelay(relay)
|
|
||||||
r.close()
|
|
||||||
} catch (err) {
|
|
||||||
/***/
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
test('removing duplicates when querying', async () => {
|
test('removing duplicates when querying', async () => {
|
||||||
let priv = generatePrivateKey()
|
let priv = generatePrivateKey()
|
||||||
let pub = getPublicKey(priv)
|
let pub = getPublicKey(priv)
|
||||||
|
|
||||||
let subs = pool.sub(relays, [
|
let sub = pool.sub(relays, [{authors: [pub]}])
|
||||||
{
|
|
||||||
authors: [pub]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
|
|
||||||
let received = []
|
let received = []
|
||||||
|
|
||||||
subs.forEach(sub =>
|
sub.on('event', event => {
|
||||||
sub.on('event', event => {
|
// this should be called only once even though we're listening
|
||||||
// this should be called only once even though we're listening
|
// to multiple relays because the events will be catched and
|
||||||
// to multiple relays because the events will be catched and
|
// deduplicated efficiently (without even being parsed)
|
||||||
// deduplicated efficiently (without even being parsed)
|
received.push(event)
|
||||||
received.push(event)
|
})
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
let event = {
|
let event = {
|
||||||
pubkey: pub,
|
pubkey: pub,
|
||||||
@@ -81,25 +54,22 @@ test('removing duplicates when querying', async () => {
|
|||||||
expect(received).toHaveLength(1)
|
expect(received).toHaveLength(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('removing duplicates correctly when double querying', async () => {
|
test('same with double querying', async () => {
|
||||||
let priv = generatePrivateKey()
|
let priv = generatePrivateKey()
|
||||||
let pub = getPublicKey(priv)
|
let pub = getPublicKey(priv)
|
||||||
|
|
||||||
let subs1 = pool.sub(relays, [{authors: [pub]}])
|
let sub1 = pool.sub(relays, [{authors: [pub]}])
|
||||||
let subs2 = pool.sub(relays, [{authors: [pub]}])
|
let sub2 = pool.sub(relays, [{authors: [pub]}])
|
||||||
|
|
||||||
let received = []
|
let received = []
|
||||||
|
|
||||||
subs1.forEach(sub =>
|
sub1.on('event', event => {
|
||||||
sub.on('event', event => {
|
received.push(event)
|
||||||
received.push(event)
|
})
|
||||||
})
|
|
||||||
)
|
sub2.on('event', event => {
|
||||||
subs2.forEach(sub =>
|
received.push(event)
|
||||||
sub.on('event', event => {
|
})
|
||||||
received.push(event)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
let event = {
|
let event = {
|
||||||
pubkey: pub,
|
pubkey: pub,
|
||||||
@@ -117,3 +87,37 @@ test('removing duplicates correctly when double querying', async () => {
|
|||||||
|
|
||||||
expect(received).toHaveLength(2)
|
expect(received).toHaveLength(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('get()', async () => {
|
||||||
|
let event = await pool.get(relays, {
|
||||||
|
ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027']
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(event).toHaveProperty(
|
||||||
|
'id',
|
||||||
|
'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('list()', async () => {
|
||||||
|
let events = await pool.list(
|
||||||
|
[...relays, 'wss://offchain.pub', 'wss://eden.nostr.land'],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
authors: [
|
||||||
|
'3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'
|
||||||
|
],
|
||||||
|
kinds: [1],
|
||||||
|
limit: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
// the actual received number will be greater than 2, but there will be no duplicates
|
||||||
|
expect(events.length).toEqual(
|
||||||
|
events
|
||||||
|
.map(evt => evt.id)
|
||||||
|
.reduce((acc, n) => (acc.indexOf(n) !== -1 ? acc : [...acc, n]), [])
|
||||||
|
.length
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|||||||
139
pool.ts
139
pool.ts
@@ -12,7 +12,16 @@ export class SimplePool {
|
|||||||
defaultRelays.forEach(this.ensureRelay)
|
defaultRelays.forEach(this.ensureRelay)
|
||||||
}
|
}
|
||||||
|
|
||||||
ensureRelay(url: string): Relay {
|
async close(relays: string[]): Promise<void> {
|
||||||
|
await Promise.all(
|
||||||
|
relays.map(async url => {
|
||||||
|
let relay = this._conn[normalizeURL(url)]
|
||||||
|
if (relay) await relay.close()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async ensureRelay(url: string): Promise<Relay> {
|
||||||
const nm = normalizeURL(url)
|
const nm = normalizeURL(url)
|
||||||
const existing = this._conn[nm]
|
const existing = this._conn[nm]
|
||||||
if (existing) return existing
|
if (existing) return existing
|
||||||
@@ -20,21 +29,74 @@ export class SimplePool {
|
|||||||
const relay = relayInit(nm)
|
const relay = relayInit(nm)
|
||||||
this._conn[nm] = relay
|
this._conn[nm] = relay
|
||||||
|
|
||||||
|
await relay.connect()
|
||||||
|
|
||||||
return relay
|
return relay
|
||||||
}
|
}
|
||||||
|
|
||||||
sub(relays: string[], filters: Filter[], opts?: SubscriptionOptions): Sub[] {
|
sub(relays: string[], filters: Filter[], opts?: SubscriptionOptions): Sub {
|
||||||
let _knownIds: Set<string> = new Set()
|
let _knownIds: Set<string> = new Set()
|
||||||
let modifiedOpts = opts || {}
|
let modifiedOpts = opts || {}
|
||||||
modifiedOpts.alreadyHaveEvent = id => _knownIds.has(id)
|
modifiedOpts.alreadyHaveEvent = id => _knownIds.has(id)
|
||||||
|
|
||||||
return relays.map(relay => {
|
let subs: Sub[] = []
|
||||||
let r = this._conn[relay]
|
let eventListeners: Set<(event: Event) => void> = new Set()
|
||||||
if (!r) return badSub()
|
let eoseListeners: Set<() => void> = new Set()
|
||||||
|
let eosesMissing = relays.length
|
||||||
|
|
||||||
|
let eoseSent = false
|
||||||
|
let eoseTimeout = setTimeout(() => {
|
||||||
|
eoseSent = true
|
||||||
|
for (let cb of eoseListeners.values()) {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}, 2400)
|
||||||
|
|
||||||
|
relays.forEach(async relay => {
|
||||||
|
let r = await this.ensureRelay(relay)
|
||||||
|
if (!r) return
|
||||||
let s = r.sub(filters, modifiedOpts)
|
let s = r.sub(filters, modifiedOpts)
|
||||||
s.on('event', (event: Event) => _knownIds.add(event.id as string))
|
s.on('event', (event: Event) => {
|
||||||
return s
|
_knownIds.add(event.id as string)
|
||||||
|
for (let cb of eventListeners.values()) {
|
||||||
|
cb(event)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
s.on('eose', () => {
|
||||||
|
if (eoseSent) return
|
||||||
|
|
||||||
|
eosesMissing--
|
||||||
|
if (eosesMissing === 0) {
|
||||||
|
clearTimeout(eoseTimeout)
|
||||||
|
for (let cb of eoseListeners.values()) {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
subs.push(s)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
let greaterSub: Sub = {
|
||||||
|
sub(filters, opts) {
|
||||||
|
subs.forEach(sub => sub.sub(filters, opts))
|
||||||
|
return greaterSub
|
||||||
|
},
|
||||||
|
unsub() {
|
||||||
|
subs.forEach(sub => sub.unsub())
|
||||||
|
},
|
||||||
|
on(type, cb) {
|
||||||
|
if (type === 'event') {
|
||||||
|
eventListeners.add(cb)
|
||||||
|
} else if (type === 'eose') eoseListeners.add(cb)
|
||||||
|
},
|
||||||
|
off(type, cb) {
|
||||||
|
if (type === 'event') {
|
||||||
|
eventListeners.delete(cb)
|
||||||
|
} else if (type === 'eose') eoseListeners.delete(cb)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return greaterSub
|
||||||
}
|
}
|
||||||
|
|
||||||
get(
|
get(
|
||||||
@@ -43,19 +105,15 @@ export class SimplePool {
|
|||||||
opts?: SubscriptionOptions
|
opts?: SubscriptionOptions
|
||||||
): Promise<Event | null> {
|
): Promise<Event | null> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let subs = this.sub(relays, [filter], opts)
|
let sub = this.sub(relays, [filter], opts)
|
||||||
let timeout = setTimeout(() => {
|
let timeout = setTimeout(() => {
|
||||||
subs.forEach(sub => sub.unsub(), 1500)
|
sub.unsub()
|
||||||
resolve(null)
|
resolve(null)
|
||||||
})
|
}, 1500)
|
||||||
subs.forEach(sub => {
|
sub.on('event', (event: Event) => {
|
||||||
sub.on('event', (event: Event) => {
|
resolve(event)
|
||||||
resolve(event)
|
clearTimeout(timeout)
|
||||||
clearTimeout(timeout)
|
sub.unsub()
|
||||||
subs.forEach(sub => {
|
|
||||||
sub.unsub()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -66,42 +124,24 @@ export class SimplePool {
|
|||||||
opts?: SubscriptionOptions
|
opts?: SubscriptionOptions
|
||||||
): Promise<Event[]> {
|
): Promise<Event[]> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let _knownIds: Set<string> = new Set()
|
|
||||||
let modifiedOpts = opts || {}
|
|
||||||
modifiedOpts.alreadyHaveEvent = id => _knownIds.has(id)
|
|
||||||
|
|
||||||
let events: Event[] = []
|
let events: Event[] = []
|
||||||
|
let sub = this.sub(relays, filters, opts)
|
||||||
|
|
||||||
let subs = this.sub(relays, filters, modifiedOpts)
|
sub.on('event', (event: Event) => {
|
||||||
let timeout = setTimeout(() => {
|
events.push(event)
|
||||||
subs.forEach(sub => sub.unsub(), 1500)
|
|
||||||
resolve(events)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
let pendingEoses = relays.length
|
// we can rely on an eose being emitted here because pool.sub() will fake one
|
||||||
|
sub.on('eose', () => {
|
||||||
subs.forEach(sub => {
|
sub.unsub()
|
||||||
sub.on('event', (event: Event) => {
|
resolve(events)
|
||||||
events.push(event)
|
|
||||||
})
|
|
||||||
|
|
||||||
sub.on('eose', () => {
|
|
||||||
pendingEoses--
|
|
||||||
if (pendingEoses === 0) {
|
|
||||||
resolve(events)
|
|
||||||
clearTimeout(timeout)
|
|
||||||
subs.forEach(sub => {
|
|
||||||
sub.unsub()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
publish(relays: string[], event: Event): Pub[] {
|
publish(relays: string[], event: Event): Pub[] {
|
||||||
return relays.map(relay => {
|
return relays.map(relay => {
|
||||||
let r = this._conn[relay]
|
let r = this._conn[normalizeURL(relay)]
|
||||||
if (!r) return badPub(relay)
|
if (!r) return badPub(relay)
|
||||||
let s = r.publish(event)
|
let s = r.publish(event)
|
||||||
return s
|
return s
|
||||||
@@ -109,17 +149,6 @@ export class SimplePool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function badSub(): Sub {
|
|
||||||
return {
|
|
||||||
on() {},
|
|
||||||
off() {},
|
|
||||||
sub(): Sub {
|
|
||||||
return badSub()
|
|
||||||
},
|
|
||||||
unsub() {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function badPub(relay: string): Pub {
|
function badPub(relay: string): Pub {
|
||||||
return {
|
return {
|
||||||
on(typ, cb) {
|
on(typ, cb) {
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ test('connectivity', () => {
|
|||||||
).resolves.toBe(true)
|
).resolves.toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('querying', () => {
|
test('querying', async () => {
|
||||||
var resolve1
|
var resolve1
|
||||||
var resolve2
|
var resolve2
|
||||||
|
|
||||||
@@ -52,16 +52,42 @@ test('querying', () => {
|
|||||||
resolve2(true)
|
resolve2(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
return expect(
|
let [t1, t2] = await Promise.all([
|
||||||
Promise.all([
|
new Promise(resolve => {
|
||||||
new Promise(resolve => {
|
resolve1 = resolve
|
||||||
resolve1 = resolve
|
}),
|
||||||
}),
|
new Promise(resolve => {
|
||||||
new Promise(resolve => {
|
resolve2 = resolve
|
||||||
resolve2 = resolve
|
})
|
||||||
})
|
])
|
||||||
])
|
|
||||||
).resolves.toEqual([true, true])
|
expect(t1).toEqual(true)
|
||||||
|
expect(t2).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('get()', async () => {
|
||||||
|
let event = await relay.get({
|
||||||
|
ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027']
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(event).toHaveProperty(
|
||||||
|
'id',
|
||||||
|
'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('list()', async () => {
|
||||||
|
let events = await relay.list([
|
||||||
|
{
|
||||||
|
authors: [
|
||||||
|
'3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d'
|
||||||
|
],
|
||||||
|
kinds: [1],
|
||||||
|
limit: 2
|
||||||
|
}
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(events.length).toEqual(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('listening (twice) and publishing', async () => {
|
test('listening (twice) and publishing', async () => {
|
||||||
|
|||||||
9
relay.ts
9
relay.ts
@@ -108,8 +108,12 @@ export function relayInit(url: string): Relay {
|
|||||||
|
|
||||||
let subid = getSubscriptionId(json)
|
let subid = getSubscriptionId(json)
|
||||||
if (subid) {
|
if (subid) {
|
||||||
let {alreadyHaveEvent} = openSubs[subid]
|
let so = openSubs[subid]
|
||||||
if (alreadyHaveEvent && alreadyHaveEvent(getHex64(json, 'id'))) {
|
if (
|
||||||
|
so &&
|
||||||
|
so.alreadyHaveEvent &&
|
||||||
|
so.alreadyHaveEvent(getHex64(json, 'id'))
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -320,6 +324,7 @@ export function relayInit(url: string): Relay {
|
|||||||
},
|
},
|
||||||
connect,
|
connect,
|
||||||
close(): Promise<void> {
|
close(): Promise<void> {
|
||||||
|
if (ws.readyState > 1) return Promise.resolve()
|
||||||
ws.close()
|
ws.close()
|
||||||
return new Promise<void>(resolve => {
|
return new Promise<void>(resolve => {
|
||||||
resolveClose = resolve
|
resolveClose = resolve
|
||||||
|
|||||||
Reference in New Issue
Block a user