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.
99 lines
2.9 KiB
TypeScript
99 lines
2.9 KiB
TypeScript
/* global WebSocket */
|
|
|
|
import { afterAll, expect, test } from 'bun:test'
|
|
|
|
import { makeAuthEvent } from './nip42.ts'
|
|
import { Relay, useWebSocketImplementation } from './relay.ts'
|
|
import { SimplePool, useWebSocketImplementation as usePoolWebSocketImplementation } from './pool.ts'
|
|
import { finalizeEvent, generateSecretKey, type Event, type EventTemplate, type VerifiedEvent } from './pure.ts'
|
|
import { MockRelay, MockWebSocketClient } from './test-helpers.ts'
|
|
|
|
const originalWebSocket = WebSocket
|
|
useWebSocketImplementation(MockWebSocketClient)
|
|
usePoolWebSocketImplementation(MockWebSocketClient)
|
|
afterAll(() => {
|
|
useWebSocketImplementation(originalWebSocket)
|
|
usePoolWebSocketImplementation(originalWebSocket)
|
|
})
|
|
|
|
test('auth flow', async () => {
|
|
const mockRelay = new MockRelay()
|
|
const relay = await Relay.connect(mockRelay.url)
|
|
const auth = makeAuthEvent(relay.url, 'chachacha')
|
|
|
|
expect(auth.tags).toHaveLength(2)
|
|
expect(auth.tags[0]).toEqual(['relay', mockRelay.url])
|
|
expect(auth.tags[1]).toEqual(['challenge', 'chachacha'])
|
|
expect(auth.kind).toEqual(22242)
|
|
})
|
|
|
|
const sk = generateSecretKey()
|
|
const sign = async (t: EventTemplate) => finalizeEvent(t, sk)
|
|
const refuse = async (): Promise<VerifiedEvent> => {
|
|
throw new Error('not now')
|
|
}
|
|
const until = async (f: () => boolean) => {
|
|
for (let i = 0; i < 200 && !f(); i++) await new Promise(r => setTimeout(r, 5))
|
|
expect(f()).toBeTrue()
|
|
}
|
|
|
|
test('auth can be retried after the signer refuses', async () => {
|
|
const mockRelay = new MockRelay()
|
|
mockRelay.authRequired = true
|
|
|
|
// refuse the challenge that comes with the connection
|
|
const relay = new Relay(mockRelay.url)
|
|
let refused = 0
|
|
relay.onauth = () => {
|
|
refused++
|
|
return refuse()
|
|
}
|
|
await relay.connect()
|
|
await until(() => refused === 1)
|
|
|
|
// refuse again, directly
|
|
await expect(relay.auth(refuse)).rejects.toThrow('not now')
|
|
expect(refused).toEqual(1)
|
|
|
|
// then sign
|
|
await expect(relay.auth(sign)).resolves.toEqual('')
|
|
relay.close()
|
|
})
|
|
|
|
test('pool: a refused auth closes the subscription, a later one authenticates', async () => {
|
|
const mockRelay = new MockRelay()
|
|
mockRelay.authRequired = true
|
|
const pool = new SimplePool()
|
|
|
|
const reasons = await new Promise<{ url: string; reason: string }[]>(resolve => {
|
|
pool.subscribeMany(
|
|
[mockRelay.url],
|
|
{ kinds: [1] },
|
|
{
|
|
onevent() {},
|
|
onauth: refuse,
|
|
onclose: resolve,
|
|
},
|
|
)
|
|
})
|
|
expect(reasons).toHaveLength(1)
|
|
expect(reasons[0].reason).toStartWith('auth was required and attempted, but failed with:')
|
|
|
|
const events: Event[] = []
|
|
await new Promise<void>(resolve => {
|
|
pool.subscribeMany(
|
|
[mockRelay.url],
|
|
{ kinds: [1] },
|
|
{
|
|
onevent: e => {
|
|
events.push(e)
|
|
},
|
|
onauth: sign,
|
|
oneose: resolve,
|
|
},
|
|
)
|
|
})
|
|
expect(events.length).toBeGreaterThan(0)
|
|
pool.close([mockRelay.url])
|
|
})
|