some fixes on relay.ts and tests.

This commit is contained in:
fiatjaf
2022-12-20 15:24:54 -03:00
parent 1a7cc5f21f
commit 53b0091bf4
4 changed files with 166 additions and 37 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ package-lock.json
standalone standalone
cjs cjs
esm esm
test.html

View File

@@ -11,10 +11,6 @@ let common = {
stream: require.resolve('readable-stream') stream: require.resolve('readable-stream')
}) })
], ],
define: {
window: 'self',
global: 'self'
},
sourcemap: 'external' sourcemap: 'external'
} }
@@ -31,6 +27,11 @@ esbuild
...common, ...common,
outdir: 'standalone/', outdir: 'standalone/',
format: 'iife', format: 'iife',
globalName: 'NostrTools' globalName: 'NostrTools',
define: {
window: 'self',
global: 'self',
process: '{"env": {}}'
}
}) })
.then(() => console.log('standalone build success.')) .then(() => console.log('standalone build success.'))

117
relay.test.js Normal file
View File

@@ -0,0 +1,117 @@
/* eslint-env jest */
const {
relayInit,
generatePrivateKey,
getPublicKey,
getEventHash,
signEvent
} = require('./cjs')
describe('relay interaction', () => {
let relay = relayInit('wss://nostr-pub.wellorder.net/')
beforeAll(() => {
relay.connect()
})
afterAll(async () => {
await relay.close()
})
test('connectivity', () => {
return expect(
new Promise(resolve => {
relay.on('connect', () => {
resolve(true)
})
relay.on('error', () => {
resolve(false)
})
})
).resolves.toBe(true)
})
test('querying', () => {
var resolve1
var resolve2
let sub = relay.sub([
{
ids: [
'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'
]
}
])
sub.on('event', event => {
expect(event).toHaveProperty(
'id',
'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'
)
resolve1(true)
})
sub.on('eose', () => {
resolve2(true)
})
return expect(
Promise.all([
new Promise(resolve => {
resolve1 = resolve
}),
new Promise(resolve => {
resolve2 = resolve
})
])
).resolves.toEqual([true, true])
})
test('listening (twice) and publishing', async () => {
let sk = generatePrivateKey()
let pk = getPublicKey(sk)
var resolve1
var resolve2
let sub = relay.sub([
{
kinds: [27572],
authors: [pk]
}
])
sub.on('event', event => {
expect(event).toHaveProperty('pubkey', pk)
expect(event).toHaveProperty('kind', 27572)
expect(event).toHaveProperty('content', 'nostr-tools test suite')
resolve1(true)
})
sub.on('event', event => {
expect(event).toHaveProperty('pubkey', pk)
expect(event).toHaveProperty('kind', 27572)
expect(event).toHaveProperty('content', 'nostr-tools test suite')
resolve2(true)
})
let event = {
kind: 27572,
pubkey: pk,
created_at: Math.floor(Date.now() / 1000),
tags: [],
content: 'nostr-tools test suite'
}
event.id = getEventHash(event)
event.sig = await signEvent(event, sk)
relay.publish(event)
return expect(
Promise.all([
new Promise(resolve => {
resolve1 = resolve
}),
new Promise(resolve => {
resolve2 = resolve
})
])
).resolves.toEqual([true, true])
})
})

View File

@@ -5,20 +5,12 @@ import 'websocket-polyfill'
import {Event, verifySignature, validateEvent} from './event' import {Event, verifySignature, validateEvent} from './event'
import {Filter, matchFilters} from './filter' import {Filter, matchFilters} from './filter'
export function normalizeRelayURL(url: string): string {
let [host, ...qs] = url.trim().split('?')
if (host.slice(0, 4) === 'http') host = 'ws' + host.slice(4)
if (host.slice(0, 2) !== 'ws') host = 'wss://' + host
if (host.length && host[host.length - 1] === '/') host = host.slice(0, -1)
return [host, ...qs].join('?')
}
export type Relay = { export type Relay = {
url: string url: string
status: number status: number
connect: () => void connect: () => void
close: () => void close: () => void
sub: (opts: SubscriptionOptions) => Sub sub: (filters: Filter[], opts: SubscriptionOptions) => Sub
publish: (event: Event) => Pub publish: (event: Event) => Pub
on: (type: 'connect' | 'disconnect' | 'notice', cb: any) => void on: (type: 'connect' | 'disconnect' | 'notice', cb: any) => void
off: (type: 'connect' | 'disconnect' | 'notice', cb: any) => void off: (type: 'connect' | 'disconnect' | 'notice', cb: any) => void
@@ -28,27 +20,25 @@ export type Pub = {
off: (type: 'ok' | 'seen' | 'failed', cb: any) => void off: (type: 'ok' | 'seen' | 'failed', cb: any) => void
} }
export type Sub = { export type Sub = {
sub: (opts: SubscriptionOptions) => Sub sub: (filters: Filter[], opts: SubscriptionOptions) => Sub
unsub: () => void unsub: () => void
on: (type: 'event' | 'eose', cb: any) => void on: (type: 'event' | 'eose', cb: any) => void
off: (type: 'event' | 'eose', cb: any) => void off: (type: 'event' | 'eose', cb: any) => void
} }
type SubscriptionOptions = { type SubscriptionOptions = {
filters: Filter[]
skipVerification?: boolean skipVerification?: boolean
id?: string id?: string
} }
export function relayInit(url: string): Relay { export function relayInit(url: string): Relay {
let relay = normalizeRelayURL(url) // set relay url
var ws: WebSocket var ws: WebSocket
var resolveOpen: () => void var resolveOpen: () => void
var resolveClose: () => void
var untilOpen: Promise<void> var untilOpen: Promise<void>
var wasClosed: boolean var wasClosed: boolean
var closed: boolean var closed: boolean
var openSubs: {[id: string]: SubscriptionOptions} = {} var openSubs: {[id: string]: {filters: Filter[]} & SubscriptionOptions} = {}
var listeners: { var listeners: {
connect: Array<() => void> connect: Array<() => void>
disconnect: Array<() => void> disconnect: Array<() => void>
@@ -65,16 +55,17 @@ export function relayInit(url: string): Relay {
event: Array<(event: Event) => void> event: Array<(event: Event) => void>
eose: Array<() => void> eose: Array<() => void>
} }
} } = {}
var pubListeners: { var pubListeners: {
[eventid: string]: { [eventid: string]: {
ok: Array<() => void> ok: Array<() => void>
seen: Array<() => void> seen: Array<() => void>
failed: Array<(reason: string) => void> failed: Array<(reason: string) => void>
} }
} } = {}
let attemptNumber = 1 let attemptNumber = 1
let nextAttemptSeconds = 1 let nextAttemptSeconds = 1
let isConnected = false
function resetOpenState() { function resetOpenState() {
untilOpen = new Promise(resolve => { untilOpen = new Promise(resolve => {
@@ -83,26 +74,37 @@ export function relayInit(url: string): Relay {
} }
function connectRelay() { function connectRelay() {
ws = new WebSocket(relay) ws = new WebSocket(url)
ws.onopen = () => { ws.onopen = () => {
listeners.connect.forEach(cb => cb()) listeners.connect.forEach(cb => cb())
resolveOpen() resolveOpen()
isConnected = true
// restablish old subscriptions // restablish old subscriptions
if (wasClosed) { if (wasClosed) {
wasClosed = false wasClosed = false
for (let id in openSubs) { for (let id in openSubs) {
sub(openSubs[id]) let {filters} = openSubs[id]
sub(filters, openSubs[id])
} }
} }
} }
ws.onerror = () => { ws.onerror = () => {
isConnected = false
listeners.error.forEach(cb => cb()) listeners.error.forEach(cb => cb())
} }
ws.onclose = async () => { ws.onclose = async () => {
isConnected = false
listeners.disconnect.forEach(cb => cb()) listeners.disconnect.forEach(cb => cb())
if (closed) return
if (closed) {
// we've closed this because we wanted, so end everything
resolveClose()
return
}
// otherwise keep trying to reconnect
resetOpenState() resetOpenState()
attemptNumber++ attemptNumber++
nextAttemptSeconds += attemptNumber ** 3 nextAttemptSeconds += attemptNumber ** 3
@@ -110,7 +112,7 @@ export function relayInit(url: string): Relay {
nextAttemptSeconds = 14400 // 4 hours nextAttemptSeconds = 14400 // 4 hours
} }
console.log( console.log(
`relay ${relay} connection closed. reconnecting in ${nextAttemptSeconds} seconds.` `relay ${url} connection closed. reconnecting in ${nextAttemptSeconds} seconds.`
) )
setTimeout(async () => { setTimeout(async () => {
try { try {
@@ -187,11 +189,13 @@ export function relayInit(url: string): Relay {
ws.send(msg) ws.send(msg)
} }
const sub = ({ const sub = (
filters, filters: Filter[],
skipVerification = false, {
id = Math.random().toString().slice(2) skipVerification = false,
}: SubscriptionOptions): Sub => { id = Math.random().toString().slice(2)
}: SubscriptionOptions = {}
): Sub => {
let subid = id let subid = id
openSubs[subid] = { openSubs[subid] = {
@@ -202,10 +206,11 @@ export function relayInit(url: string): Relay {
trySend(['REQ', subid, ...filters]) trySend(['REQ', subid, ...filters])
return { return {
sub: ({ sub: (newFilters, newOpts = {}) =>
filters = openSubs[subid].filters, sub(newFilters || filters, {
skipVerification = openSubs[subid].skipVerification skipVerification: newOpts.skipVerification || skipVerification,
}) => sub({filters, skipVerification, id: subid}), id: subid
}),
unsub: () => { unsub: () => {
delete openSubs[subid] delete openSubs[subid]
delete subListeners[subid] delete subListeners[subid]
@@ -230,6 +235,9 @@ export function relayInit(url: string): Relay {
sub, sub,
on: (type: 'connect' | 'disconnect' | 'notice', cb: any): void => { on: (type: 'connect' | 'disconnect' | 'notice', cb: any): void => {
listeners[type].push(cb) listeners[type].push(cb)
if (type === 'connect' && isConnected) {
cb()
}
}, },
off: (type: 'connect' | 'disconnect' | 'notice', cb: any): void => { off: (type: 'connect' | 'disconnect' | 'notice', cb: any): void => {
let index = listeners[type].indexOf(cb) let index = listeners[type].indexOf(cb)
@@ -253,8 +261,7 @@ export function relayInit(url: string): Relay {
.catch(() => {}) .catch(() => {})
const startMonitoring = () => { const startMonitoring = () => {
let monitor = sub({ let monitor = sub([{ids: [id]}], {
filters: [{ids: [id]}],
id: `monitor-${id.slice(0, 5)}` id: `monitor-${id.slice(0, 5)}`
}) })
let willUnsub = setTimeout(() => { let willUnsub = setTimeout(() => {
@@ -290,9 +297,12 @@ export function relayInit(url: string): Relay {
} }
}, },
connect, connect,
close() { close(): Promise<void> {
closed = true // prevent ws from trying to reconnect closed = true // prevent ws from trying to reconnect
ws.close() ws.close()
return new Promise(resolve => {
resolveClose = resolve
})
}, },
get status() { get status() {
return ws.readyState return ws.readyState