mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2026-10-05 14:38:23 +00:00
auth() caught a throwing signer, logged it and never settled the promise, which stayed cached for the connection: every later auth() on that relay got the same pending promise, so a refused challenge left auth-required subscriptions unreported and publishes waiting forever. A relay's OK false or a timeout stayed cached the same way. Now a failed attempt rejects (the pool reports it through onclose and the publish rejection as before) and the next call tries again. The automatic path on AUTH logs the failure instead of rethrowing it. The mock relay speaks NIP-42 for the tests.
141 lines
3.8 KiB
TypeScript
141 lines
3.8 KiB
TypeScript
import { Server, WebSocket } from 'mock-socket'
|
|
|
|
import { finalizeEvent, type Event, getPublicKey, generateSecretKey } from './pure.ts'
|
|
import { matchFilters, type Filter } from './filter.ts'
|
|
|
|
export const MockWebSocketClient = WebSocket
|
|
|
|
export function buildEvent(params: Partial<Event>): Event {
|
|
return {
|
|
id: '',
|
|
kind: 1,
|
|
pubkey: '',
|
|
created_at: 0,
|
|
content: '',
|
|
tags: [],
|
|
sig: '',
|
|
...params,
|
|
}
|
|
}
|
|
|
|
let serial = 0
|
|
|
|
export class MockRelay {
|
|
private _server: Server
|
|
|
|
public url: string
|
|
public secretKeys: Uint8Array[]
|
|
public preloadedEvents: Event[]
|
|
public unresponsive: boolean = false
|
|
public authRequired: boolean = false
|
|
public challenge: string = 'mock-challenge'
|
|
|
|
constructor(url?: string | undefined) {
|
|
serial++
|
|
this.url = url ?? `wss://random.mock.relay/${serial}`
|
|
this.secretKeys = [generateSecretKey(), generateSecretKey(), generateSecretKey(), generateSecretKey()]
|
|
this.preloadedEvents = this.secretKeys.map(sk =>
|
|
finalizeEvent(
|
|
{
|
|
kind: 1,
|
|
content: 'autogenerated by relay',
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
tags: [['t', 'auto']],
|
|
},
|
|
sk,
|
|
),
|
|
)
|
|
|
|
this._server = new Server(this.url)
|
|
this._server.on('connection', (conn: any) => {
|
|
let subs: { [subId: string]: { conn: any; filters: Filter[] } } = {}
|
|
let authenticated = false
|
|
if (this.authRequired) conn.send(JSON.stringify(['AUTH', this.challenge]))
|
|
|
|
conn.on('message', (message: string) => {
|
|
if (this.unresponsive) return
|
|
const data = JSON.parse(message)
|
|
|
|
switch (data[0]) {
|
|
case 'AUTH': {
|
|
let event = data[1]
|
|
const ok =
|
|
event.kind === 22242 && event.tags.some((t: string[]) => t[0] === 'challenge' && t[1] === this.challenge)
|
|
if (ok) authenticated = true
|
|
conn.send(JSON.stringify(['OK', event.id, ok, ok ? '' : 'auth-required: bad challenge']))
|
|
|
|
break
|
|
}
|
|
case 'REQ': {
|
|
let subId = data[1]
|
|
let filters = data.slice(2)
|
|
if (this.authRequired && !authenticated) {
|
|
conn.send(JSON.stringify(['CLOSED', subId, 'auth-required: you need to authenticate']))
|
|
|
|
break
|
|
}
|
|
subs[subId] = { conn, filters }
|
|
|
|
this.preloadedEvents.forEach(event => {
|
|
conn.send(JSON.stringify(['EVENT', subId, event]))
|
|
})
|
|
|
|
filters.forEach((filter: Filter) => {
|
|
const kinds = filter.kinds?.length ? filter.kinds : [1]
|
|
|
|
kinds.forEach(kind => {
|
|
this.secretKeys.forEach(sk => {
|
|
const event = finalizeEvent(
|
|
{
|
|
kind,
|
|
content: 'kind-aware autogenerated by relay',
|
|
created_at: Math.floor(Date.now() / 1000),
|
|
tags: [['t', 'auto']],
|
|
},
|
|
sk,
|
|
)
|
|
|
|
conn.send(JSON.stringify(['EVENT', subId, event]))
|
|
})
|
|
})
|
|
})
|
|
|
|
conn.send(JSON.stringify(['EOSE', subId]))
|
|
|
|
break
|
|
}
|
|
case 'CLOSE': {
|
|
let subId = data[1]
|
|
delete subs[subId]
|
|
|
|
break
|
|
}
|
|
case 'EVENT': {
|
|
let event = data[1]
|
|
|
|
conn.send(JSON.stringify(['OK', event.id, 'true']))
|
|
|
|
for (let subId in subs) {
|
|
const { filters, conn: listener } = subs[subId]
|
|
|
|
if (matchFilters(filters, event)) {
|
|
listener.send(JSON.stringify(['EVENT', subId, event]))
|
|
}
|
|
}
|
|
|
|
break
|
|
}
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
get authors() {
|
|
return this.secretKeys.map(getPublicKey)
|
|
}
|
|
|
|
get ids() {
|
|
return this.preloadedEvents.map(evt => evt.id)
|
|
}
|
|
}
|