-
Notifications
You must be signed in to change notification settings - Fork 9
/
server.js
108 lines (100 loc) · 3.25 KB
/
server.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#!/usr/bin/env node
import fastify from 'fastify'
import fastifyWebsocket from '@fastify/websocket'
import createSwarm from './swarm.js'
import * as SDK from 'hyper-sdk'
import goodbye from './goodbye.js'
import { validateEvent, getEventType } from './nostr_events.js'
const port = process.argv[2] || 3000
const startingTopics = process.argv.slice(3)
const sdk = await SDK.create({
storage: '.hyper-nostr-relay',
autoJoin: true
})
console.log('your key is', sdk.publicKey.toString('hex'))
goodbye(async _ => {
console.log('exiting...')
await sdk.close().catch(e => {
console.error(e)
throw e
})
})
const fi = fastify()
goodbye(_ => fi.close())
const topics = new Map()
await Promise.all(
startingTopics
.filter(Boolean)
.map(async topic => {
const swarm = await createSwarm(sdk, topic)
topics.set(topic, swarm)
return swarm.update()
})
)
fi.register(fastifyWebsocket)
fi.register(async function (fastify) {
fastify.route({
method: 'GET',
url: '/:topic',
handler: (req, reply) => {
const { topic } = req.params
if (req.headers.accept === 'application/nostr+json') {
reply.send({
name: 'nostr-relay-' + (topic || 'nostr'),
description: 'a decentralized nostr relay, powered by Hypercore',
pubkey: 'd5b4107402ea8a23719f8c7fc57e7eaba6bc54e7c2da62b39300207c156978f1',
'supported-nips': [1, 2, 9, 11, 12, 16, 20, 33, 45],
software: 'https://github.com/Ruulul/hyper-nostr'
})
} else reply.send()
},
wsHandler: async (con, req) => {
let { topic } = req.params
if (!topic) topic = 'nostr'
if (!topics.has(topic)) {
topics.set(topic, await createSwarm(sdk, topic))
}
const { sendEvent, subscriptions, queryEvents, sendQueryToSubscription } = topics.get(topic)
const { socket } = con
socket.on('message', async message => {
const [type, value, ...rest] = JSON.parse(message)
switch (type) {
case 'EVENT': {
if (!validateEvent(value)) {
socket.send('["NOTICE", "Invalid event"]')
break
}
const type = getEventType(value.kind)
if (!type) {
socket.send('["NOTICE", "Unrecognized event kind"]')
break
}
sendEvent(value)
socket.send(`["OK", ${value.id}, true, ""]`)
break
}
case 'REQ':
subscriptions.set(value, { filters: rest, socket, receivedEvents: new Set() })
await sendQueryToSubscription(subscriptions.get(value), value)
socket.send(`["EOSE", "${value}"]`)
break
case 'CLOSE':
subscriptions.delete(value)
break
case 'COUNT':
socket.send(`["COUNT", "${value}", ${JSON.stringify({ count: (await queryEvents(rest)).length })}]`)
break
default:
socket.send('["NOTICE", "Unrecognized event"]')
}
})
socket.once('close', _ => {
subscriptions.forEach(({ socket: _socket }, key) => socket === _socket && subscriptions.delete(key))
})
}
})
})
fi.listen({ port, host: '0.0.0.0' }, err => {
if (err) throw err
console.log(`listening on ${port}`)
})