211 lines
4.2 KiB
JavaScript
211 lines
4.2 KiB
JavaScript
const RelayPool = (function nostrlib() {
|
|
const WS = typeof WebSocket !== 'undefined' ? WebSocket : require('ws')
|
|
|
|
function RelayPool(relays, opts)
|
|
{
|
|
if (!(this instanceof RelayPool))
|
|
return new RelayPool(relays)
|
|
|
|
this.onfn = {}
|
|
this.relays = []
|
|
|
|
for (const relay of relays) {
|
|
this.add(relay)
|
|
}
|
|
|
|
return this
|
|
}
|
|
|
|
RelayPool.prototype.close = function relayPoolClose() {
|
|
for (const relay of this.relays) {
|
|
relay.close()
|
|
}
|
|
}
|
|
|
|
RelayPool.prototype.on = function relayPoolOn(method, fn) {
|
|
for (const relay of this.relays) {
|
|
this.onfn[method] = fn
|
|
relay.onfn[method] = fn.bind(null, relay)
|
|
}
|
|
}
|
|
|
|
RelayPool.prototype.has = function relayPoolHas(relayUrl) {
|
|
for (const relay of this.relays) {
|
|
if (relay.relay === relayUrl)
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
RelayPool.prototype.setupHandlers = function relayPoolSetupHandlers(method, fn)
|
|
{
|
|
// setup its message handlers with the ones we have already
|
|
for (const handler of Object.keys(this.on)) {
|
|
for (const relay of this.relays) {
|
|
relay.onfn[handler] = this.onfn[handler].bind(null, relay)
|
|
}
|
|
}
|
|
}
|
|
|
|
RelayPool.prototype.remove = function relayPoolRemove(url) {
|
|
let i = 0
|
|
|
|
for (const relay of this.relays) {
|
|
if (relay.url === url) {
|
|
relay.ws && relay.ws.close()
|
|
this.relays = this.replays.splice(i, 1)
|
|
return true
|
|
}
|
|
|
|
i += 1
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
RelayPool.prototype.subscribe = function relayPoolSubscribe(...args) {
|
|
for (const relay of this.relays) {
|
|
relay.subscribe(...args)
|
|
}
|
|
}
|
|
|
|
RelayPool.prototype.unsubscribe = function relayPoolUnsubscibe(...args) {
|
|
for (const relay of this.relays) {
|
|
relay.unsubscribe(...args)
|
|
}
|
|
}
|
|
|
|
RelayPool.prototype.add = function relayPoolAdd(relay) {
|
|
if (relay instanceof Relay) {
|
|
if (this.has(relay.url))
|
|
return false
|
|
|
|
this.relays.push(relay)
|
|
this.setupHandlers()
|
|
return true
|
|
}
|
|
|
|
if (this.has(relay))
|
|
return false
|
|
|
|
const r = Relay(relay, this.opts)
|
|
this.relays.push(r)
|
|
this.setupHandlers()
|
|
return true
|
|
}
|
|
|
|
function Relay(relay, opts={})
|
|
{
|
|
if (!(this instanceof Relay))
|
|
return new Relay(relay, opts)
|
|
|
|
this.url = relay
|
|
this.opts = opts
|
|
|
|
if (opts.reconnect == null)
|
|
opts.reconnect = true
|
|
|
|
const me = this
|
|
me.onfn = {}
|
|
|
|
init_websocket(me)
|
|
|
|
return this
|
|
}
|
|
|
|
function init_websocket(me) {
|
|
const ws = me.ws = new WS(me.url);
|
|
return new Promise((resolve, reject) => {
|
|
let resolved = false
|
|
ws.onmessage = (m) => { handle_nostr_message(me, m) }
|
|
ws.onclose = () => {
|
|
if (me.onfn.close)
|
|
me.onfn.close()
|
|
if (me.reconnecting)
|
|
return reject(new Error("close during reconnect"))
|
|
if (!me.manualClose && me.opts.reconnect)
|
|
reconnect(me)
|
|
}
|
|
ws.onerror = () => {
|
|
if (me.onfn.error)
|
|
me.onfn.error()
|
|
if (me.reconnecting)
|
|
return reject(new Error("error during reconnect"))
|
|
if (me.opts.reconnect)
|
|
reconnect(me)
|
|
}
|
|
ws.onopen = () => {
|
|
if (me.onfn.open)
|
|
me.onfn.open()
|
|
|
|
if (resolved) return
|
|
|
|
resolved = true
|
|
resolve(me)
|
|
}
|
|
});
|
|
}
|
|
|
|
function sleep(ms) {
|
|
return new Promise(resolve => setTimeout(resolve, ms));
|
|
}
|
|
|
|
async function reconnect(me)
|
|
{
|
|
const reconnecting = true
|
|
let n = 100
|
|
try {
|
|
me.reconnecting = true
|
|
await init_websocket(me)
|
|
me.reconnecting = false
|
|
} catch {
|
|
console.error(`error thrown during reconnect... trying again in ${n} ms`)
|
|
await sleep(n)
|
|
n *= 1.5
|
|
}
|
|
}
|
|
|
|
Relay.prototype.on = function relayOn(method, fn) {
|
|
this.onfn[method] = fn
|
|
}
|
|
|
|
Relay.prototype.close = function relayClose() {
|
|
if (this.ws) {
|
|
this.manualClose = true
|
|
this.ws.close()
|
|
}
|
|
}
|
|
|
|
Relay.prototype.subscribe = function relay_subscribe(sub_id, ...filters) {
|
|
const tosend = ["REQ", sub_id, ...filters]
|
|
this.ws.send(JSON.stringify(tosend))
|
|
}
|
|
|
|
Relay.prototype.unsubscribe = function relay_unsubscribe(sub_id) {
|
|
const tosend = ["CLOSE", sub_id]
|
|
this.ws.send(JSON.stringify(tosend))
|
|
}
|
|
|
|
function handle_nostr_message(relay, msg)
|
|
{
|
|
const data = JSON.parse(msg.data)
|
|
if (data.length >= 2) {
|
|
switch (data[0]) {
|
|
case "EVENT":
|
|
if (data.length < 3)
|
|
return
|
|
return relay.onfn.event && relay.onfn.event(data[1], data[2])
|
|
case "EOSE":
|
|
return relay.onfn.eose && relay.onfn.eose(data[1])
|
|
case "NOTICE":
|
|
return relay.onfn.notice && relay.onfn.notice(...data.slice(1))
|
|
}
|
|
}
|
|
}
|
|
|
|
return RelayPool
|
|
})()
|
|
|
|
if (typeof module !== 'undefined' && module.exports)
|
|
module.exports = RelayPool
|