mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-09 00:28:51 +00:00
some fixes on relay.ts and tests.
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,3 +6,4 @@ package-lock.json
|
|||||||
standalone
|
standalone
|
||||||
cjs
|
cjs
|
||||||
esm
|
esm
|
||||||
|
test.html
|
||||||
|
|||||||
11
build.cjs
11
build.cjs
@@ -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
117
relay.test.js
Normal 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])
|
||||||
|
})
|
||||||
|
})
|
||||||
74
relay.ts
74
relay.ts
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user