mirror of
https://github.com/coracle-social/coracle.git
synced 2024-10-06 11:43:30 +00:00
Add new pool based on paravel
This commit is contained in:
parent
56a5d17116
commit
e9da4e899b
@ -1,7 +1,7 @@
|
|||||||
# Current
|
# Current
|
||||||
|
|
||||||
|
- [ ] Fix reactions and replies
|
||||||
- [ ] Multiplexer
|
- [ ] Multiplexer
|
||||||
- [ ] Explore the idea of separating everything into different components and wiring it all up into a system in a single file.
|
|
||||||
- [ ] Write NIP to support proxies. Update COUNT NIP to mention how proxies are a good use case for COUNT
|
- [ ] Write NIP to support proxies. Update COUNT NIP to mention how proxies are a good use case for COUNT
|
||||||
- [ ] Fix iOS/safari/firefox
|
- [ ] Fix iOS/safari/firefox
|
||||||
- [ ] https://github.com/staab/coracle/issues/42
|
- [ ] https://github.com/staab/coracle/issues/42
|
||||||
@ -136,3 +136,4 @@
|
|||||||
- Graph view? Query db with COUNT? Hardware specs on relay info endpoint?
|
- Graph view? Query db with COUNT? Hardware specs on relay info endpoint?
|
||||||
- "adoptarelay.com"
|
- "adoptarelay.com"
|
||||||
- Add suggested relays based on follows or topics
|
- Add suggested relays based on follows or topics
|
||||||
|
- [ ] Integrate plephy https://plebhy.com/
|
||||||
|
BIN
package-lock.json
generated
BIN
package-lock.json
generated
Binary file not shown.
@ -46,6 +46,7 @@
|
|||||||
"lru-cache": "^7.18.3",
|
"lru-cache": "^7.18.3",
|
||||||
"nostr-tools": "^1.7.4",
|
"nostr-tools": "^1.7.4",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
|
"paravel": "^0.1.7",
|
||||||
"qr-scanner": "^1.4.2",
|
"qr-scanner": "^1.4.2",
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
"ramda": "^0.28.0",
|
"ramda": "^0.28.0",
|
||||||
|
@ -83,13 +83,16 @@
|
|||||||
|
|
||||||
$: style.textContent = `:root { ${getThemeVariables($theme)}; background: var(--gray-8); }`
|
$: style.textContent = `:root { ${getThemeVariables($theme)}; background: var(--gray-8); }`
|
||||||
|
|
||||||
// When we get an AUTH challenge from our pool, attempt to authenticate
|
const seenChallenges = new Set()
|
||||||
pool.eventBus.on("AUTH", async (challenge, connection) => {
|
|
||||||
const publishable = cmd.authenticate(challenge, url)
|
|
||||||
const [event] = await publishable.publish([{url: connection.url}])
|
|
||||||
|
|
||||||
connection.checkAuth(event.id)
|
// When we get an AUTH challenge from our pool, attempt to authenticate
|
||||||
})
|
pool.Config.authHandler = async (url, challenge) => {
|
||||||
|
if (!seenChallenges.has(challenge)) {
|
||||||
|
seenChallenges.add(challenge)
|
||||||
|
|
||||||
|
return first(await cmd.authenticate(url, challenge).publish([{url}]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
// Keep scroll position on body, but don't allow scrolling
|
// Keep scroll position on body, but don't allow scrolling
|
||||||
|
@ -7,11 +7,11 @@ import pool from "src/agent/pool"
|
|||||||
import sync from "src/agent/sync"
|
import sync from "src/agent/sync"
|
||||||
import keys from "src/agent/keys"
|
import keys from "src/agent/keys"
|
||||||
|
|
||||||
const authenticate = (challenge, relay) =>
|
const authenticate = (url, challenge) =>
|
||||||
new PublishableEvent(22242, {
|
new PublishableEvent(22242, {
|
||||||
tags: [
|
tags: [
|
||||||
["challenge", challenge],
|
["challenge", challenge],
|
||||||
["relay", relay],
|
["relay", url],
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ const processMentions = map(pubkey => {
|
|||||||
const name = displayPerson(getPersonWithFallback(pubkey))
|
const name = displayPerson(getPersonWithFallback(pubkey))
|
||||||
const relay = getRelayForPersonHint(pubkey)
|
const relay = getRelayForPersonHint(pubkey)
|
||||||
|
|
||||||
return ["p", pubkey, relay?.url || '', name]
|
return ["p", pubkey, relay?.url || "", name]
|
||||||
})
|
})
|
||||||
|
|
||||||
const getReplyTags = n => {
|
const getReplyTags = n => {
|
||||||
@ -132,8 +132,11 @@ class PublishableEvent {
|
|||||||
|
|
||||||
this.event = {kind, content, tags, pubkey, created_at: createdAt}
|
this.event = {kind, content, tags, pubkey, created_at: createdAt}
|
||||||
}
|
}
|
||||||
|
getSignedEvent() {
|
||||||
|
return keys.sign(this.event)
|
||||||
|
}
|
||||||
async publish(relays, onProgress = null) {
|
async publish(relays, onProgress = null) {
|
||||||
const event = await keys.sign(this.event)
|
const event = await this.getSignedEvent()
|
||||||
const promise = pool.publish({relays, event, onProgress})
|
const promise = pool.publish({relays, event, onProgress})
|
||||||
|
|
||||||
sync.processEvents(event)
|
sync.processEvents(event)
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import type {MyEvent} from "src/util/types"
|
import type {MyEvent} from "src/util/types"
|
||||||
import {sortBy, assoc, uniq, uniqBy, prop, propEq, reject, groupBy, pluck} from "ramda"
|
import {sortBy, assoc, uniq, uniqBy, prop, propEq, groupBy, pluck} from "ramda"
|
||||||
import {personKinds, findReplyId} from "src/util/nostr"
|
import {personKinds, findReplyId} from "src/util/nostr"
|
||||||
import {log} from "src/util/logger"
|
|
||||||
import {chunk} from "hurdak/lib/hurdak"
|
import {chunk} from "hurdak/lib/hurdak"
|
||||||
import {batch, now, timedelta} from "src/util/misc"
|
import {batch, now, timedelta} from "src/util/misc"
|
||||||
import {
|
import {
|
||||||
@ -42,6 +41,7 @@ const listen = ({relays, filter, onChunk = null, shouldProcess = true, delay = 5
|
|||||||
|
|
||||||
const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 5000}) => {
|
const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 5000}) => {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
let completed = false
|
||||||
const done = new Set()
|
const done = new Set()
|
||||||
const allEvents = []
|
const allEvents = []
|
||||||
|
|
||||||
@ -49,25 +49,16 @@ const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 5
|
|||||||
const sub = await subPromise
|
const sub = await subPromise
|
||||||
|
|
||||||
// If we've already unsubscribed we're good
|
// If we've already unsubscribed we're good
|
||||||
if (!sub.isActive()) {
|
if (completed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDone = done.size === relays.length
|
const isDone = done.size === relays.length
|
||||||
|
|
||||||
if (force) {
|
if (force) {
|
||||||
const timedOutRelays = reject(r => done.has(r.url), relays)
|
relays.forEach(relay => {
|
||||||
|
if (!done.has(relay.url)) {
|
||||||
log(
|
pool.Meta.onTimeout(relay.url)
|
||||||
`Timing out ${timedOutRelays.length}/${relays.length} relays after ${timeout}ms`,
|
|
||||||
timedOutRelays
|
|
||||||
)
|
|
||||||
|
|
||||||
timedOutRelays.forEach(relay => {
|
|
||||||
const conn = pool.getConnection(relay.url)
|
|
||||||
|
|
||||||
if (conn) {
|
|
||||||
conn.stats.timeouts += 1
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -75,6 +66,7 @@ const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 5
|
|||||||
if (isDone || force) {
|
if (isDone || force) {
|
||||||
sub.unsub()
|
sub.unsub()
|
||||||
resolve(allEvents)
|
resolve(allEvents)
|
||||||
|
completed = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,10 +93,6 @@ const load = ({relays, filter, onChunk = null, shouldProcess = true, timeout = 5
|
|||||||
done.add(url)
|
done.add(url)
|
||||||
attemptToComplete(false)
|
attemptToComplete(false)
|
||||||
},
|
},
|
||||||
onError: url => {
|
|
||||||
done.add(url)
|
|
||||||
attemptToComplete(false)
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}) as Promise<MyEvent[]>
|
}) as Promise<MyEvent[]>
|
||||||
}
|
}
|
||||||
|
@ -1,246 +1,137 @@
|
|||||||
import type {Relay, Filter} from "nostr-tools"
|
import type {Relay, Filter} from "nostr-tools"
|
||||||
import type {Deferred} from "src/util/misc"
|
|
||||||
import type {MyEvent} from "src/util/types"
|
import type {MyEvent} from "src/util/types"
|
||||||
|
import {Socket, Pool, Plex, Relays, Executor} from "paravel"
|
||||||
import {verifySignature} from "nostr-tools"
|
import {verifySignature} from "nostr-tools"
|
||||||
import {pluck, objOf, identity, is} from "ramda"
|
import {pluck, identity} from "ramda"
|
||||||
import {ensurePlural, noop} from "hurdak/lib/hurdak"
|
import {ensurePlural, switcher} from "hurdak/lib/hurdak"
|
||||||
import {warn, log, error} from "src/util/logger"
|
import {warn, log, error} from "src/util/logger"
|
||||||
import {union, EventBus, defer, tryJson, difference} from "src/util/misc"
|
import {union, difference} from "src/util/misc"
|
||||||
import {isRelay, normalizeRelayUrl} from "src/util/nostr"
|
import {normalizeRelayUrl} from "src/util/nostr"
|
||||||
|
|
||||||
const forceRelays = (import.meta.env.VITE_FORCE_RELAYS || "")
|
const Config = {
|
||||||
.split(",")
|
multiplextrUrl: null,
|
||||||
.filter(identity)
|
authHandler: null,
|
||||||
.map(objOf("url"))
|
|
||||||
|
|
||||||
// Connection management
|
|
||||||
|
|
||||||
const eventBus = new EventBus()
|
|
||||||
|
|
||||||
const connections = {}
|
|
||||||
|
|
||||||
const STATUS = {
|
|
||||||
NEW: "new",
|
|
||||||
PENDING: "pending",
|
|
||||||
CLOSED: "closed",
|
|
||||||
ERROR: "error",
|
|
||||||
READY: "ready",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ERROR = {
|
const Meta = {
|
||||||
CONNECTION: "connection",
|
stats: {},
|
||||||
UNAUTHORIZED: "unauthorized",
|
errors: {},
|
||||||
FORBIDDEN: "forbidden",
|
getStats(url) {
|
||||||
}
|
if (!this.stats[url]) {
|
||||||
|
this.stats[url] = {
|
||||||
class Connection {
|
error: null,
|
||||||
ws?: WebSocket
|
|
||||||
url: string
|
|
||||||
promise?: Deferred<void>
|
|
||||||
queue: string[]
|
|
||||||
error: {code: string; message: string, occurredAt: number}
|
|
||||||
status: {code: string; message: string}
|
|
||||||
timeout?: number
|
|
||||||
stats: Record<string, number>
|
|
||||||
bus: EventBus
|
|
||||||
constructor(url) {
|
|
||||||
if (connections[url]) {
|
|
||||||
error(`Connection to ${url} already exists`)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws = null
|
|
||||||
this.url = url
|
|
||||||
this.promise = null
|
|
||||||
this.queue = []
|
|
||||||
this.timeout = null
|
|
||||||
this.bus = new EventBus()
|
|
||||||
this.error = null
|
|
||||||
this.stats = {
|
|
||||||
timeouts: 0,
|
timeouts: 0,
|
||||||
subsCount: 0,
|
subsCount: 0,
|
||||||
eoseCount: 0,
|
eoseCount: 0,
|
||||||
eoseTimer: 0,
|
eoseTimer: 0,
|
||||||
eventsCount: 0,
|
eventsCount: 0,
|
||||||
activeSubsCount: 0,
|
activeSubsCount: 0,
|
||||||
}
|
lastRequest: 0,
|
||||||
|
openedAt: 0,
|
||||||
this.status = {code: STATUS.NEW, message: "Waiting to connect"}
|
closedAt: 0,
|
||||||
this.listenForAuth()
|
|
||||||
|
|
||||||
connections[url] = this
|
|
||||||
}
|
|
||||||
setStatus(code, message) {
|
|
||||||
this.status = {code, message}
|
|
||||||
}
|
|
||||||
setError(code, message) {
|
|
||||||
this.error = {code, message, occurredAt: Date.now()}
|
|
||||||
}
|
|
||||||
connect() {
|
|
||||||
if (this.ws) {
|
|
||||||
error("Attempted to connect when already connected", this)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.promise = defer()
|
|
||||||
this.ws = new WebSocket(this.url)
|
|
||||||
this.setStatus(STATUS.PENDING, "Trying to connect")
|
|
||||||
|
|
||||||
this.ws.addEventListener("open", () => {
|
|
||||||
log(`Opened connection to ${this.url}`)
|
|
||||||
|
|
||||||
this.setStatus(STATUS.READY, "Connected")
|
|
||||||
this.promise.resolve()
|
|
||||||
})
|
|
||||||
|
|
||||||
this.ws.addEventListener("message", e => {
|
|
||||||
this.queue.push(e.data)
|
|
||||||
|
|
||||||
if (!this.timeout) {
|
|
||||||
this.timeout = window.setTimeout(() => this.handleMessages(), 10)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.ws.addEventListener("error", e => {
|
|
||||||
log(`Error on connection to ${this.url}`)
|
|
||||||
|
|
||||||
this.disconnect()
|
|
||||||
this.promise.reject()
|
|
||||||
this.setError(ERROR.CONNECTION, "Disconnected")
|
|
||||||
this.setStatus(STATUS.CLOSED, "Closed")
|
|
||||||
})
|
|
||||||
|
|
||||||
this.ws.addEventListener("close", () => {
|
|
||||||
log(`Closed connection to ${this.url}`)
|
|
||||||
|
|
||||||
this.disconnect()
|
|
||||||
this.promise.reject()
|
|
||||||
this.setStatus(STATUS.CLOSED, "Closed")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
disconnect() {
|
|
||||||
if (this.ws) {
|
|
||||||
log(`Disconnecting from ${this.url}`)
|
|
||||||
|
|
||||||
this.ws.close()
|
|
||||||
this.ws = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async autoConnect() {
|
|
||||||
// If the connection has not been opened, or was closed, open 'er up
|
|
||||||
if (!this.error && [STATUS.NEW, STATUS.CLOSED].includes(this.status.code)) {
|
|
||||||
this.connect()
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the connection failed, try to re-open after a while
|
|
||||||
if (this.error?.code === ERROR.CONNECTION && Date.now() - 30_000 > this.error.occurredAt) {
|
|
||||||
this.disconnect()
|
|
||||||
this.connect()
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.promise.catch(noop)
|
|
||||||
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
handleMessages() {
|
|
||||||
for (const json of this.queue.splice(0, 10)) {
|
|
||||||
const message = tryJson(() => JSON.parse(json))
|
|
||||||
|
|
||||||
if (message) {
|
|
||||||
const [k, ...payload] = message
|
|
||||||
|
|
||||||
this.bus.handle(k, ...payload)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.timeout = this.queue.length > 0 ? window.setTimeout(() => this.handleMessages(), 10) : null
|
return this.stats[url]
|
||||||
}
|
|
||||||
send(...payload) {
|
|
||||||
if (this.ws?.readyState !== 1) {
|
|
||||||
warn("Send attempted before socket was ready", this)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.ws.send(JSON.stringify(payload))
|
|
||||||
}
|
|
||||||
subscribe(filters, id, {onEvent, onEose}) {
|
|
||||||
const [eventChannel, eoseChannel] = [
|
|
||||||
this.bus.on("EVENT", (subid, e) => subid === id && onEvent(e)),
|
|
||||||
this.bus.on("EOSE", subid => subid === id && onEose()),
|
|
||||||
]
|
|
||||||
|
|
||||||
this.send("REQ", id, ...filters)
|
|
||||||
|
|
||||||
return {
|
|
||||||
conn: this,
|
|
||||||
unsub: () => {
|
|
||||||
if (this.status.code === STATUS.READY) {
|
|
||||||
this.send("CLOSE", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.bus.off("EVENT", eventChannel)
|
|
||||||
this.bus.off("EOSE", eoseChannel)
|
|
||||||
},
|
},
|
||||||
|
onPublish(urls) {
|
||||||
|
urls.forEach(url => {
|
||||||
|
const stats = this.getStats(url)
|
||||||
|
|
||||||
|
stats.lastRequest = Date.now()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onSubscriptionStart(urls) {
|
||||||
|
urls.forEach(url => {
|
||||||
|
const stats = this.getStats(url)
|
||||||
|
|
||||||
|
stats.subsCount += 1
|
||||||
|
stats.activeSubsCount += 1
|
||||||
|
stats.lastRequest = Date.now()
|
||||||
|
|
||||||
|
if (stats.activeSubsCount > 10) {
|
||||||
|
warn(`Relay ${url} has >10 active subscriptions`)
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
publish(event, {onOk, onError}) {
|
},
|
||||||
const withCleanup = cb => k => {
|
onSubscriptionEnd(urls) {
|
||||||
if (k === event.id) {
|
urls.forEach(url => {
|
||||||
cb()
|
const stats = this.getStats(url)
|
||||||
this.bus.off("OK", okChannel)
|
|
||||||
this.bus.off("ERROR", errorChannel)
|
stats.activeSubsCount -= 1
|
||||||
}
|
})
|
||||||
|
},
|
||||||
|
onEvent(url) {
|
||||||
|
const stats = this.getStats(url)
|
||||||
|
|
||||||
|
stats.eventsCount += 1
|
||||||
|
},
|
||||||
|
onEose(url, ms) {
|
||||||
|
const stats = this.getStats(url)
|
||||||
|
|
||||||
|
stats.eoseCount += 1
|
||||||
|
stats.eoseTimer += ms
|
||||||
|
},
|
||||||
|
onTimeout(url) {
|
||||||
|
const stats = this.getStats(url)
|
||||||
|
|
||||||
|
stats.timeouts += 1
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const forceUrls = (import.meta.env.VITE_FORCE_RELAYS || "").split(",").filter(identity)
|
||||||
|
|
||||||
|
const getUrls = relays => {
|
||||||
|
if (relays.length === 0) {
|
||||||
|
error(`Attempted to connect to zero urls`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const [okChannel, errorChannel] = [
|
const urls = new Set(pluck("url", relays).map(normalizeRelayUrl))
|
||||||
this.bus.on("OK", withCleanup(onOk)),
|
|
||||||
this.bus.on("ERROR", withCleanup(onError)),
|
if (urls.size !== relays.length) {
|
||||||
|
warn(`Attempted to connect to non-unique relays`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(urls)
|
||||||
|
}
|
||||||
|
|
||||||
|
const pool = new Pool()
|
||||||
|
|
||||||
|
pool.bus.addListeners({
|
||||||
|
open: ({url}) => {
|
||||||
|
const stats = Meta.getStats(url)
|
||||||
|
|
||||||
|
stats.openedAt = Date.now()
|
||||||
|
},
|
||||||
|
close: ({url}) => {
|
||||||
|
const stats = Meta.getStats(url)
|
||||||
|
|
||||||
|
stats.closedAt = Date.now()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function disconnect(url) {
|
||||||
|
pool.remove(url)
|
||||||
|
|
||||||
|
delete Meta.stats[url]
|
||||||
|
delete Meta.errors[url]
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQuality(url) {
|
||||||
|
if (Meta.errors[url]) {
|
||||||
|
return [
|
||||||
|
0,
|
||||||
|
switcher(Meta.errors[url], {
|
||||||
|
disconnected: "Disconnected",
|
||||||
|
unauthorized: "Logging in",
|
||||||
|
forbidden: "Failed to log in",
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
this.send("EVENT", event)
|
|
||||||
}
|
|
||||||
count(filter, id, {onCount}) {
|
|
||||||
const channel = this.bus.on("COUNT", (subid, ...payload) => {
|
|
||||||
if (subid === id) {
|
|
||||||
onCount(...payload)
|
|
||||||
|
|
||||||
this.bus.off("COUNT", channel)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
this.send("COUNT", id, ...filter)
|
|
||||||
}
|
|
||||||
listenForAuth() {
|
|
||||||
// Propagate auth to global handler
|
|
||||||
this.bus.on("AUTH", challenge => {
|
|
||||||
if (!this.error) {
|
|
||||||
this.setStatus(STATUS.ERROR, "Logging in")
|
|
||||||
this.setError(ERROR.UNAUTHORIZED, "Logging in")
|
|
||||||
|
|
||||||
eventBus.handle("AUTH", challenge, this)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
checkAuth(eid) {
|
|
||||||
const channel = this.bus.on("OK", (id, ok, message) => {
|
|
||||||
if (id === eid) {
|
|
||||||
if (ok) {
|
|
||||||
this.setStatus(STATUS.READY, "Connected")
|
|
||||||
} else {
|
|
||||||
this.disconnect()
|
|
||||||
this.setError(ERROR.FORBIDDEN, "Access restricted")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.bus.off("OK", channel)
|
const stats = Meta.getStats(url)
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
hasRecentError() {
|
|
||||||
return this.error && Date.now() - 30_000 < this.error.occurredAt
|
|
||||||
}
|
|
||||||
getQuality() {
|
|
||||||
if (this.error) {
|
|
||||||
return [0, this.error.message]
|
|
||||||
}
|
|
||||||
|
|
||||||
const {timeouts, subsCount, eoseTimer, eoseCount} = this.stats
|
const {timeouts, subsCount, eoseTimer, eoseCount} = stats
|
||||||
const timeoutRate = timeouts > 0 ? timeouts / subsCount : null
|
const timeoutRate = timeouts > 0 ? timeouts / subsCount : null
|
||||||
const eoseQuality = eoseCount > 0 ? Math.max(1, 500 / (eoseTimer / eoseCount)) : null
|
const eoseQuality = eoseCount > 0 ? Math.max(1, 500 / (eoseTimer / eoseCount)) : null
|
||||||
|
|
||||||
@ -256,63 +147,47 @@ class Connection {
|
|||||||
return [eoseQuality, "Connected"]
|
return [eoseQuality, "Connected"]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.status.code === STATUS.READY) {
|
if (pool.get(url).status === Socket.STATUS.READY) {
|
||||||
return [1, "Connected"]
|
return [1, "Connected"]
|
||||||
}
|
}
|
||||||
|
|
||||||
return [0.5, this.status.message]
|
return [0.5, "Not Connected"]
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getConnections = () => connections
|
function getExecutor(urls) {
|
||||||
|
if (forceUrls.length > 0) {
|
||||||
const getConnection = url => connections[url]
|
urls = forceUrls
|
||||||
|
|
||||||
const connect = url => {
|
|
||||||
if (!isRelay(url)) {
|
|
||||||
warn(`Invalid relay url ${url}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url !== normalizeRelayUrl(url)) {
|
const executor = new Executor(
|
||||||
warn(`Received non-normalized relay url ${url}`)
|
Config.multiplextrUrl
|
||||||
}
|
? new Plex(urls, pool.get(Config.multiplextrUrl))
|
||||||
|
: new Relays(urls.map(url => pool.get(url)))
|
||||||
|
)
|
||||||
|
|
||||||
if (!connections[url]) {
|
executor.handleAuth({
|
||||||
connections[url] = new Connection(url)
|
onAuth(url, challenge) {
|
||||||
}
|
Meta.errors[url] = "unauthorized"
|
||||||
|
|
||||||
return connections[url].autoConnect()
|
return Config.authHandler(url, challenge)
|
||||||
|
},
|
||||||
|
onOk(url, id, ok, message) {
|
||||||
|
Meta.errors[url] = ok ? null : "forbidden"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
return executor
|
||||||
}
|
}
|
||||||
|
|
||||||
const disconnect = url => {
|
async function publish({relays, event, onProgress, timeout = 3000}) {
|
||||||
if (connections[url]) {
|
const urls = getUrls(relays)
|
||||||
connections[url].disconnect()
|
const executor = getExecutor(urls)
|
||||||
|
|
||||||
delete connections[url]
|
Meta.onPublish(urls)
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public api - publish/subscribe
|
log(`Publishing to ${urls.length} relays`, event, urls)
|
||||||
|
|
||||||
const publish = async ({relays, event, onProgress, timeout = 5000}) => {
|
|
||||||
if (forceRelays.length > 0) {
|
|
||||||
relays = forceRelays
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relays.length === 0) {
|
|
||||||
error(`Attempted to publish to zero relays`, event)
|
|
||||||
} else {
|
|
||||||
log(`Publishing to ${relays.length} relays`, event, relays)
|
|
||||||
}
|
|
||||||
|
|
||||||
const urls = new Set(pluck("url", relays))
|
|
||||||
|
|
||||||
if (urls.size !== relays.length) {
|
|
||||||
warn(`Attempted to publish to non-unique relays`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
let resolved = false
|
|
||||||
const timeouts = new Set()
|
const timeouts = new Set()
|
||||||
const succeeded = new Set()
|
const succeeded = new Set()
|
||||||
const failed = new Set()
|
const failed = new Set()
|
||||||
@ -325,26 +200,20 @@ const publish = async ({relays, event, onProgress, timeout = 5000}) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const attemptToResolve = () => {
|
const attemptToResolve = () => {
|
||||||
// Don't report progress once we're done, even if more errors/ok come through
|
|
||||||
if (resolved) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const progress = getProgress()
|
const progress = getProgress()
|
||||||
|
|
||||||
if (onProgress) {
|
|
||||||
onProgress(progress)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (progress.pending.size === 0) {
|
if (progress.pending.size === 0) {
|
||||||
log(`Finished publishing to ${urls.size} relays`, event, progress)
|
log(`Finished publishing to ${urls.length} relays`, event, progress)
|
||||||
resolve(progress)
|
resolve(progress)
|
||||||
resolved = true
|
sub.unsubscribe()
|
||||||
|
executor.target.cleanup()
|
||||||
|
} else if (onProgress) {
|
||||||
|
onProgress(progress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
for (const {url} of relays) {
|
for (const url of urls) {
|
||||||
if (!succeeded.has(url) && !failed.has(url)) {
|
if (!succeeded.has(url) && !failed.has(url)) {
|
||||||
timeouts.add(url)
|
timeouts.add(url)
|
||||||
}
|
}
|
||||||
@ -353,29 +222,21 @@ const publish = async ({relays, event, onProgress, timeout = 5000}) => {
|
|||||||
attemptToResolve()
|
attemptToResolve()
|
||||||
}, timeout)
|
}, timeout)
|
||||||
|
|
||||||
relays.map(async relay => {
|
const sub = executor.publish(event, {
|
||||||
const conn = await connect(relay.url)
|
onOk: url => {
|
||||||
|
succeeded.add(url)
|
||||||
if (conn.status.code === STATUS.READY || conn.error.code === ERROR.UNAUTHORIZED) {
|
timeouts.delete(url)
|
||||||
conn.publish(event, {
|
failed.delete(url)
|
||||||
onOk: () => {
|
|
||||||
succeeded.add(relay.url)
|
|
||||||
timeouts.delete(relay.url)
|
|
||||||
failed.delete(relay.url)
|
|
||||||
attemptToResolve()
|
attemptToResolve()
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: url => {
|
||||||
failed.add(relay.url)
|
failed.add(url)
|
||||||
timeouts.delete(relay.url)
|
timeouts.delete(url)
|
||||||
attemptToResolve()
|
attemptToResolve()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
failed.add(relay.url)
|
|
||||||
attemptToResolve()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
|
// Report progress to start
|
||||||
attemptToResolve()
|
attemptToResolve()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -384,54 +245,27 @@ type SubscribeOpts = {
|
|||||||
relays: Relay[]
|
relays: Relay[]
|
||||||
filter: Filter[] | Filter
|
filter: Filter[] | Filter
|
||||||
onEvent: (event: MyEvent) => void
|
onEvent: (event: MyEvent) => void
|
||||||
onError?: (url: string) => void
|
|
||||||
onEose?: (url: string) => void
|
onEose?: (url: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const subscribe = async ({relays, filter, onEvent, onEose, onError}: SubscribeOpts) => {
|
async function subscribe({relays, filter, onEvent, onEose}: SubscribeOpts) {
|
||||||
filter = ensurePlural(filter)
|
const urls = getUrls(relays)
|
||||||
|
const executor = getExecutor(urls)
|
||||||
if (forceRelays.length > 0) {
|
const filters = ensurePlural(filter)
|
||||||
relays = forceRelays
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = createFilterId(filter)
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const seen = new Set()
|
const seen = new Set()
|
||||||
const eose = new Set()
|
const eose = new Set()
|
||||||
|
|
||||||
let active = true
|
log(`Starting subscription with ${relays.length} relays`, filters, relays)
|
||||||
|
|
||||||
if (relays.length === 0) {
|
|
||||||
error(`Attempted to start subscription ${id} with zero relays`, filter)
|
|
||||||
} else {
|
|
||||||
log(`Starting subscription ${id} with ${relays.length} relays`, filter, relays)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (relays.length !== new Set(pluck("url", relays)).size) {
|
if (relays.length !== new Set(pluck("url", relays)).size) {
|
||||||
error(`Subscribed to non-unique relays`, relays)
|
error(`Subscribed to non-unique relays`, relays)
|
||||||
}
|
}
|
||||||
|
|
||||||
const promises = relays.map(async relay => {
|
Meta.onSubscriptionStart(urls)
|
||||||
const conn = await connect(relay.url)
|
|
||||||
|
|
||||||
if (conn.status.code !== STATUS.READY) {
|
const sub = executor.subscribe(filters, {
|
||||||
if (onError) {
|
onEvent: (url, e) => {
|
||||||
onError(relay.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
conn.stats.subsCount += 1
|
|
||||||
conn.stats.activeSubsCount += 1
|
|
||||||
|
|
||||||
if (conn.stats.activeSubsCount > 10) {
|
|
||||||
warn(`Relay ${conn.url} has >10 active subscriptions`)
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn.subscribe(filter, id, {
|
|
||||||
onEvent: e => {
|
|
||||||
if (seen.has(e.id)) {
|
if (seen.has(e.id)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -442,93 +276,57 @@ const subscribe = async ({relays, filter, onEvent, onEose, onError}: SubscribeOp
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
conn.stats.eventsCount += 1
|
Meta.onEvent(url)
|
||||||
|
|
||||||
onEvent({...e, seen_on: relay.url, content: e.content || ""})
|
onEvent({...e, seen_on: url, content: e.content || ""})
|
||||||
},
|
},
|
||||||
onEose: () => {
|
onEose: url => {
|
||||||
if (onEose) {
|
onEose?.(url)
|
||||||
onEose(conn.url)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keep track of relay timing stats, but only for the first eose we get
|
// Keep track of relay timing stats, but only for the first eose we get
|
||||||
if (!eose.has(conn.url)) {
|
if (!eose.has(url)) {
|
||||||
eose.add(conn.url)
|
Meta.onEose(url, Date.now() - now)
|
||||||
|
|
||||||
conn.stats.eoseCount += 1
|
|
||||||
conn.stats.eoseTimer += Date.now() - now
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
eose.add(url)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isActive: () => active,
|
|
||||||
unsub: () => {
|
unsub: () => {
|
||||||
if (!active) {
|
log(`Closing subscription`, filters)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
active = false
|
sub.unsubscribe()
|
||||||
|
executor.target.cleanup()
|
||||||
|
|
||||||
log(`Closing subscription ${id}`)
|
Meta.onSubscriptionEnd(urls)
|
||||||
|
|
||||||
promises.forEach(async promise => {
|
|
||||||
const sub = await promise
|
|
||||||
|
|
||||||
if (sub) {
|
|
||||||
sub.unsub()
|
|
||||||
sub.conn.stats.activeSubsCount -= 1
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = async filter => {
|
async function count(filter) {
|
||||||
const conn = await connect("wss://rbr.bio")
|
const filters = ensurePlural(filter)
|
||||||
|
const executor = getExecutor(["wss://rbr.bio"])
|
||||||
if (!conn || conn.status.code !== STATUS.READY) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
filter = ensurePlural(filter)
|
|
||||||
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
conn.count(filter, createFilterId(filter), {
|
const sub = executor.count(filters, {
|
||||||
onCount: res => resolve(res?.count),
|
onCount: res => resolve(res?.count),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve(0)
|
||||||
|
sub.unsubscribe()
|
||||||
|
executor.target.cleanup()
|
||||||
|
}, 3000)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utils
|
|
||||||
|
|
||||||
const createFilterId = filters =>
|
|
||||||
[Math.random().toString().slice(2, 6), filters.map(describeFilter).join(":")].join("-")
|
|
||||||
|
|
||||||
const describeFilter = ({kinds = [], ...filter}) => {
|
|
||||||
const parts = []
|
|
||||||
|
|
||||||
parts.push(kinds.join(","))
|
|
||||||
|
|
||||||
for (const [key, value] of Object.entries(filter)) {
|
|
||||||
if (is(Array, value)) {
|
|
||||||
parts.push(`${key}[${value.length}]`)
|
|
||||||
} else {
|
|
||||||
parts.push(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "(" + parts.join(",") + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
eventBus,
|
Config,
|
||||||
forceRelays,
|
Meta,
|
||||||
getConnections,
|
forceUrls,
|
||||||
getConnection,
|
|
||||||
connect,
|
|
||||||
disconnect,
|
disconnect,
|
||||||
|
getQuality,
|
||||||
publish,
|
publish,
|
||||||
subscribe,
|
subscribe,
|
||||||
count,
|
count,
|
||||||
|
@ -160,8 +160,11 @@ export const sampleRelays = (relays, scale = 1) => {
|
|||||||
limit *= scale
|
limit *= scale
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove relays that are currently in an error state
|
// Remove relays that are currently in an error state or were recently closed
|
||||||
relays = relays.filter(r => !pool.getConnection(r.url)?.hasRecentError())
|
relays = relays.filter(r => {
|
||||||
|
if (pool.Meta.errors[r.url]) return false
|
||||||
|
if (pool.Meta.getStats(r.url).closed > Date.now() - 30_000) return false
|
||||||
|
})
|
||||||
|
|
||||||
// Limit target relays
|
// Limit target relays
|
||||||
relays = relays.slice(0, limit)
|
relays = relays.slice(0, limit)
|
||||||
|
@ -18,6 +18,7 @@ import {findReplyId, findRootId} from "src/util/nostr"
|
|||||||
import {synced} from "src/util/misc"
|
import {synced} from "src/util/misc"
|
||||||
import {derived} from "svelte/store"
|
import {derived} from "svelte/store"
|
||||||
import keys from "src/agent/keys"
|
import keys from "src/agent/keys"
|
||||||
|
import pool from "src/agent/pool"
|
||||||
import cmd from "src/agent/cmd"
|
import cmd from "src/agent/cmd"
|
||||||
|
|
||||||
const profile = synced("agent/user/profile", {
|
const profile = synced("agent/user/profile", {
|
||||||
@ -54,6 +55,7 @@ let profileCopy = null
|
|||||||
|
|
||||||
profile.subscribe($profile => {
|
profile.subscribe($profile => {
|
||||||
profileCopy = $profile
|
profileCopy = $profile
|
||||||
|
pool.Config.multiplextrUrl = $profile.settings.multiplextrUrl
|
||||||
})
|
})
|
||||||
|
|
||||||
// Watch pubkey and add to profile
|
// Watch pubkey and add to profile
|
||||||
|
@ -11,14 +11,14 @@ setInterval(() => {
|
|||||||
const relayUrls = new Set(pluck("url", getUserRelays()))
|
const relayUrls = new Set(pluck("url", getUserRelays()))
|
||||||
|
|
||||||
// Prune connections we haven't used in a while
|
// Prune connections we haven't used in a while
|
||||||
Object.values(pool.getConnections())
|
Object.entries(pool.Meta.stats)
|
||||||
.filter(conn => conn.lastRequest < Date.now() - 60_000)
|
.filter(([url, stats]) => stats.lastRequest < Date.now() - 60_000)
|
||||||
.forEach(conn => conn.nostr.close())
|
.forEach(([url, stats]) => pool.disconnect(url))
|
||||||
|
|
||||||
// Alert the user to any heinously slow connections
|
// Alert the user to any heinously slow connections
|
||||||
slowConnections.set(
|
slowConnections.set(
|
||||||
Object.values(pool.getConnections()).filter(
|
Object.keys(pool.Meta.stats).filter(
|
||||||
c => relayUrls.has(c.url) && first(c.getQuality()) < 0.3
|
url => relayUrls.has(url) && first(pool.getQuality(url)) < 0.3
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}, 30_000)
|
}, 30_000)
|
||||||
|
@ -19,14 +19,7 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
return poll(10_000, async () => {
|
return poll(10_000, async () => {
|
||||||
const conn = await pool.getConnection(relay.url)
|
;[quality, message] = pool.getQuality(relay.url)
|
||||||
|
|
||||||
if (conn) {
|
|
||||||
;[quality, message] = conn.getQuality()
|
|
||||||
} else {
|
|
||||||
quality = null
|
|
||||||
message = "Not connected"
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
const interpolate = (a, b) => t => a + Math.round((b - a) * t)
|
const interpolate = (a, b) => t => a + Math.round((b - a) * t)
|
||||||
const {petnamePubkeys, canPublish, mutes} = user
|
const {petnamePubkeys, canPublish, mutes} = user
|
||||||
const getRelays = () => sampleRelays(relays.concat(getPubkeyWriteRelays(pubkey)))
|
const getRelays = () => sampleRelays(relays.concat(getPubkeyWriteRelays(pubkey)))
|
||||||
const tabs = ["notes", "likes", pool.forceRelays.length === 0 && "relays"].filter(identity)
|
const tabs = ["notes", "likes", pool.forceUrls.length === 0 && "relays"].filter(identity)
|
||||||
|
|
||||||
let pubkey = toHex(npub)
|
let pubkey = toHex(npub)
|
||||||
let following = false
|
let following = false
|
||||||
@ -79,7 +79,7 @@
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pool.forceRelays.length === 0) {
|
if (pool.forceUrls.length === 0) {
|
||||||
actions.push({onClick: openProfileInfo, label: "Profile", icon: "info"})
|
actions.push({onClick: openProfileInfo, label: "Profile", icon: "info"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,14 +26,7 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
return poll(10_000, async () => {
|
return poll(10_000, async () => {
|
||||||
const conn = await pool.getConnection(relay.url)
|
;[quality, message] = pool.getQuality(relay.url)
|
||||||
|
|
||||||
if (conn) {
|
|
||||||
;[quality, message] = conn.getQuality()
|
|
||||||
} else {
|
|
||||||
quality = null
|
|
||||||
message = "Not connected"
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -89,7 +89,7 @@
|
|||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="mx-3 my-4 h-px bg-gray-6" />
|
<li class="mx-3 my-4 h-px bg-gray-6" />
|
||||||
{#if pool.forceRelays.length === 0}
|
{#if pool.forceUrls.length === 0}
|
||||||
<li class="relative cursor-pointer">
|
<li class="relative cursor-pointer">
|
||||||
<a class="block px-4 py-2 transition-all hover:bg-accent hover:text-white" href="/relays">
|
<a class="block px-4 py-2 transition-all hover:bg-accent hover:text-white" href="/relays">
|
||||||
<i class="fa fa-server mr-2" /> Relays
|
<i class="fa fa-server mr-2" /> Relays
|
||||||
|
@ -115,7 +115,7 @@
|
|||||||
}}>here</Anchor
|
}}>here</Anchor
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
{#if pool.forceRelays.length > 0}
|
{#if pool.forceUrls.length > 0}
|
||||||
<Spinner />
|
<Spinner />
|
||||||
{:else if Object.values(currentRelays).length > 0}
|
{:else if Object.values(currentRelays).length > 0}
|
||||||
<p>Currently searching:</p>
|
<p>Currently searching:</p>
|
||||||
|
@ -450,7 +450,7 @@
|
|||||||
let:instance
|
let:instance
|
||||||
class="flex flex-col gap-2"
|
class="flex flex-col gap-2"
|
||||||
on:click={() => instance.hide()}>
|
on:click={() => instance.hide()}>
|
||||||
{#if pool.forceRelays.length === 0}
|
{#if pool.forceUrls.length === 0}
|
||||||
<Anchor
|
<Anchor
|
||||||
type="button-circle"
|
type="button-circle"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {uniq} from "ramda"
|
import {uniq, objOf} from "ramda"
|
||||||
import {onMount} from "svelte"
|
import {onMount} from "svelte"
|
||||||
import {generatePrivateKey} from "nostr-tools"
|
import {generatePrivateKey} from "nostr-tools"
|
||||||
import {fly} from "svelte/transition"
|
import {fly} from "svelte/transition"
|
||||||
@ -28,8 +28,8 @@
|
|||||||
let relays = []
|
let relays = []
|
||||||
if ($userRelays.length > 0) {
|
if ($userRelays.length > 0) {
|
||||||
relays = $userRelays
|
relays = $userRelays
|
||||||
} else if (pool.forceRelays.length > 0) {
|
} else if (pool.forceUrls.length > 0) {
|
||||||
relays = pool.forceRelays
|
relays = pool.forceUrls.map(objOf("url"))
|
||||||
} else {
|
} else {
|
||||||
relays = [
|
relays = [
|
||||||
{url: "wss://nostr-pub.wellorder.net", write: true},
|
{url: "wss://nostr-pub.wellorder.net", write: true},
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
export let privkey
|
export let privkey
|
||||||
|
|
||||||
const nsec = nip19.nsecEncode(privkey)
|
const nsec = nip19.nsecEncode(privkey)
|
||||||
const nextStage = pool.forceRelays.length > 0 ? "follows" : "relays"
|
const nextStage = pool.forceUrls.length > 0 ? "follows" : "relays"
|
||||||
|
|
||||||
const copyKey = () => {
|
const copyKey = () => {
|
||||||
copyToClipboard(nsec)
|
copyToClipboard(nsec)
|
||||||
|
@ -35,14 +35,7 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
return poll(10_000, async () => {
|
return poll(10_000, async () => {
|
||||||
const conn = await pool.getConnection(relay.url)
|
;[quality, message] = await pool.getQuality(relay.url)
|
||||||
|
|
||||||
if (conn) {
|
|
||||||
;[quality, message] = conn.getQuality()
|
|
||||||
} else {
|
|
||||||
quality = null
|
|
||||||
message = "Not connected"
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user