2023-07-27 11:26:26 -04:00
|
|
|
// from https://github.com/paulmillr/noble-secp256k1/blob/main/index.ts#L803
|
|
|
|
function hexToBytes(hex) {
|
2024-01-28 12:50:44 -05:00
|
|
|
if (typeof hex !== 'string') {
|
|
|
|
throw new TypeError('hexToBytes: expected string, got ' + typeof hex)
|
2023-07-27 11:26:26 -04:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
if (hex.length % 2)
|
|
|
|
throw new Error('hexToBytes: received invalid unpadded hex' + hex.length)
|
|
|
|
const array = new Uint8Array(hex.length / 2)
|
|
|
|
for (let i = 0; i < array.length; i++) {
|
|
|
|
const j = i * 2
|
|
|
|
const hexByte = hex.slice(j, j + 2)
|
|
|
|
const byte = Number.parseInt(hexByte, 16)
|
|
|
|
if (Number.isNaN(byte) || byte < 0) throw new Error('Invalid byte sequence')
|
|
|
|
array[i] = byte
|
2023-07-27 11:26:26 -04:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
return array
|
|
|
|
}
|
|
|
|
|
|
|
|
// decode nip19 ('npub') to hex
|
|
|
|
const npub2hexa = (npub) => {
|
|
|
|
let { prefix, words } = bech32.bech32.decode(npub, 90)
|
|
|
|
if (prefix === 'npub') {
|
|
|
|
let data = new Uint8Array(bech32.bech32.fromWords(words))
|
|
|
|
return buffer.Buffer.from(data).toString('hex')
|
2023-07-27 11:26:26 -04:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
2023-07-27 11:26:26 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// encode hex to nip19 ('npub')
|
|
|
|
const hexa2npub = (hex) => {
|
|
|
|
const data = hexToBytes(hex)
|
|
|
|
const words = bech32.bech32.toWords(data)
|
|
|
|
const prefix = 'npub'
|
|
|
|
return bech32.bech32.encode(prefix, words, 90)
|
|
|
|
}
|
2023-07-27 11:26:26 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// parse inserted pubkey
|
|
|
|
const parsePubkey = (pubkey) =>
|
|
|
|
pubkey.match('npub1') ? npub2hexa(pubkey) : pubkey
|
|
|
|
|
2024-01-29 13:46:55 -05:00
|
|
|
const parseRelaySet = (commaSeparatedRelayString, defaultSet) => {
|
2024-01-28 13:51:21 -05:00
|
|
|
let list = commaSeparatedRelayString.split(",")
|
|
|
|
|
2024-01-29 13:46:55 -05:00
|
|
|
if (list && list.length > 0 && list[0] !== "")
|
|
|
|
return list.map((it) => it.trim())
|
|
|
|
else
|
|
|
|
return defaultSet
|
2024-01-28 13:51:21 -05:00
|
|
|
}
|
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// download js file
|
|
|
|
const downloadFile = (data, fileName) => {
|
2024-01-28 17:28:21 -05:00
|
|
|
const prettyJs = JSON.stringify(data, null, 2)
|
2024-01-28 12:50:44 -05:00
|
|
|
const tempLink = document.createElement('a')
|
2024-01-28 17:28:21 -05:00
|
|
|
const taBlob = new Blob([prettyJs], { type: 'text/json' })
|
2024-01-28 12:50:44 -05:00
|
|
|
tempLink.setAttribute('href', URL.createObjectURL(taBlob))
|
|
|
|
tempLink.setAttribute('download', fileName)
|
|
|
|
tempLink.click()
|
|
|
|
}
|
2023-08-03 14:55:52 -04:00
|
|
|
|
2024-01-28 19:41:32 -05:00
|
|
|
const updateRelayStatus = (relay, status, addToCount, subscription, until, message, relayStatusAndCount) => {
|
2024-01-28 12:50:44 -05:00
|
|
|
if (relayStatusAndCount[relay] == undefined) {
|
|
|
|
relayStatusAndCount[relay] = {}
|
|
|
|
}
|
2023-08-03 18:57:56 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
if (status)
|
|
|
|
relayStatusAndCount[relay].status = status
|
2023-08-03 18:57:56 -04:00
|
|
|
|
2024-01-28 15:44:09 -05:00
|
|
|
if (!relayStatusAndCount[relay].until) {
|
|
|
|
relayStatusAndCount[relay].until = {}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (subscription)
|
|
|
|
relayStatusAndCount[relay].until[subscription] = until
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 19:41:32 -05:00
|
|
|
if (message)
|
|
|
|
relayStatusAndCount[relay].message = message
|
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
if (relayStatusAndCount[relay].count != undefined)
|
|
|
|
relayStatusAndCount[relay].count = relayStatusAndCount[relay].count + addToCount
|
|
|
|
else
|
|
|
|
relayStatusAndCount[relay].count = addToCount
|
2023-08-03 18:57:56 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
displayRelayStatus(relayStatusAndCount)
|
|
|
|
}
|
2023-08-03 18:57:56 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
const displayRelayStatus = (relayStatusAndCount) => {
|
|
|
|
if (Object.keys(relayStatusAndCount).length > 0) {
|
2024-01-28 15:44:09 -05:00
|
|
|
Object.keys(relayStatusAndCount).forEach(
|
2024-01-28 12:50:44 -05:00
|
|
|
it => {
|
|
|
|
let untilStr = "";
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 15:44:09 -05:00
|
|
|
if (relayStatusAndCount[it].until) {
|
2024-01-28 15:59:28 -05:00
|
|
|
if (relayStatusAndCount[it].until["my-sub-0"])
|
|
|
|
untilStr += "<td> <" + new Date(relayStatusAndCount[it].until["my-sub-0"] * 1000).toLocaleDateString("en-US") + "</td>"
|
|
|
|
else
|
|
|
|
untilStr += "<td> </td>"
|
|
|
|
|
|
|
|
if (relayStatusAndCount[it].until["my-sub-1"])
|
|
|
|
untilStr += "<td> <" + new Date(relayStatusAndCount[it].until["my-sub-1"] * 1000).toLocaleDateString("en-US") + "</td>"
|
|
|
|
else
|
|
|
|
untilStr += "<td> </td>"
|
|
|
|
} else {
|
|
|
|
untilStr += "<td> </td> <td> </td>"
|
2024-01-28 15:44:09 -05:00
|
|
|
}
|
2024-01-28 19:41:32 -05:00
|
|
|
|
|
|
|
let msg = ""
|
|
|
|
|
|
|
|
if (relayStatusAndCount[it].message)
|
|
|
|
msg = relayStatusAndCount[it].message
|
2024-01-28 15:44:09 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
const relayName = it.replace("wss://", "").replace("ws://", "")
|
2024-01-28 19:41:32 -05:00
|
|
|
const line = "<td>" + relayName + "</td><td>" + relayStatusAndCount[it].status + "</td>" + untilStr + "<td>" + relayStatusAndCount[it].count + "</td>" + "<td>" + msg + "</td>"
|
2024-01-28 15:44:09 -05:00
|
|
|
|
2024-03-12 08:34:38 -04:00
|
|
|
const elemId = relayName.replaceAll(".", "").replaceAll("/", "-")
|
2024-01-28 15:44:09 -05:00
|
|
|
|
|
|
|
if ($('#' + elemId).length > 0) {
|
|
|
|
$('#' + elemId).html(line)
|
|
|
|
} else {
|
|
|
|
$('#checking-relays').append(
|
|
|
|
$("<tr>" +line+ "</tr>").attr('id', elemId)
|
|
|
|
)
|
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
2024-01-28 15:44:09 -05:00
|
|
|
)
|
2024-01-28 12:50:44 -05:00
|
|
|
} else {
|
|
|
|
$('#checking-relays-header').html("")
|
2024-01-28 19:30:04 -05:00
|
|
|
$('#checking-relays').html("<tr id=\"checking-relays-header\"></tr>")
|
2023-08-03 14:55:52 -04:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
2023-08-03 14:55:52 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// fetch events from relay, returns a promise
|
2024-01-28 17:28:21 -05:00
|
|
|
const fetchFromRelay = async (relay, filters, addedFilters, pubkey, events, relayStatus) =>
|
2024-01-28 12:50:44 -05:00
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
try {
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Starting", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
// open websocket
|
|
|
|
const ws = new WebSocket(relay)
|
2023-08-02 18:25:29 -04:00
|
|
|
|
2024-01-28 14:27:08 -05:00
|
|
|
let isAuthenticating = false
|
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// prevent hanging forever
|
|
|
|
let myTimeout = setTimeout(() => {
|
|
|
|
ws.close()
|
|
|
|
reject(relay)
|
|
|
|
}, 10_000)
|
|
|
|
|
|
|
|
const subscriptions = Object.fromEntries(filters.map ( (filter, index) => {
|
|
|
|
let id = "my-sub-"+index
|
|
|
|
|
2024-01-28 17:28:21 -05:00
|
|
|
let myFilter = filter
|
|
|
|
|
|
|
|
if (!myFilter.since && addedFilters.since) {
|
|
|
|
myFilter.since = addedFilters.since
|
|
|
|
}
|
|
|
|
if (!myFilter.until && addedFilters.until) {
|
|
|
|
myFilter.until = addedFilters.until
|
|
|
|
}
|
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
return [
|
|
|
|
id, {
|
|
|
|
id: id,
|
|
|
|
counter: 0,
|
|
|
|
lastEvent: null,
|
|
|
|
done: false,
|
2024-01-28 17:28:21 -05:00
|
|
|
filter: myFilter,
|
2024-01-28 14:27:08 -05:00
|
|
|
eventIds: new Set()
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
|
|
|
]
|
|
|
|
}))
|
|
|
|
|
|
|
|
// subscribe to events filtered by author
|
|
|
|
ws.onopen = () => {
|
|
|
|
clearTimeout(myTimeout)
|
|
|
|
myTimeout = setTimeout(() => {
|
2023-08-02 18:25:29 -04:00
|
|
|
ws.close()
|
2024-01-28 12:43:23 -05:00
|
|
|
reject(relay)
|
2023-08-03 14:55:52 -04:00
|
|
|
}, 10_000)
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Downloading", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
for (const [key, sub] of Object.entries(subscriptions)) {
|
|
|
|
ws.send(JSON.stringify(['REQ', sub.id, sub.filter]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Listen for messages
|
|
|
|
ws.onmessage = (event) => {
|
|
|
|
const [msgType, subscriptionId, data] = JSON.parse(event.data)
|
|
|
|
// event messages
|
|
|
|
if (msgType === 'EVENT') {
|
2023-08-03 14:55:52 -04:00
|
|
|
clearTimeout(myTimeout)
|
|
|
|
myTimeout = setTimeout(() => {
|
|
|
|
ws.close()
|
2024-01-28 12:43:23 -05:00
|
|
|
reject(relay)
|
2023-08-03 14:55:52 -04:00
|
|
|
}, 10_000)
|
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
try {
|
|
|
|
const { id } = data
|
2023-08-03 15:54:07 -04:00
|
|
|
|
2024-01-28 17:28:21 -05:00
|
|
|
if (addedFilters.since && data.created_at < addedFilters.since) return
|
|
|
|
if (addedFilters.until && data.created_at > addedFilters.until) return
|
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
if (!subscriptions[subscriptionId].lastEvent || data.created_at < subscriptions[subscriptionId].lastEvent.created_at)
|
|
|
|
subscriptions[subscriptionId].lastEvent = data
|
2023-08-03 15:54:07 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
if (data.id in subscriptions[subscriptionId].eventIds) return
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
subscriptions[subscriptionId].eventIds.add(data.id)
|
|
|
|
subscriptions[subscriptionId].counter++
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// don't save/reboradcast kind 3s that are not from the author.
|
|
|
|
// their are too big.
|
|
|
|
if (data.kind == 3 && data.pubkey != pubkey) {
|
|
|
|
return
|
|
|
|
}
|
2023-08-03 18:57:56 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
let until = undefined
|
2023-08-03 18:57:56 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
if (subscriptions[subscriptionId].lastEvent) {
|
|
|
|
until = subscriptions[subscriptionId].lastEvent.created_at
|
2024-01-28 12:43:23 -05:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, undefined, 1, subscriptionId, until, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
|
|
|
|
// prevent duplicated events
|
|
|
|
if (events[id]) return
|
|
|
|
else events[id] = data
|
|
|
|
|
|
|
|
// show how many events were found until this moment
|
|
|
|
$('#events-found').text(`${Object.keys(events).length} events found`)
|
|
|
|
} catch(err) {
|
|
|
|
console.log(err, event)
|
|
|
|
return
|
2023-07-27 11:26:26 -04:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// end of subscription messages
|
|
|
|
if (msgType === 'EOSE') {
|
|
|
|
// Restarting the filter is necessary to go around Max Limits for each relay.
|
|
|
|
if (subscriptions[subscriptionId].counter < 2) {
|
|
|
|
subscriptions[subscriptionId].done = true
|
2024-01-28 18:32:10 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
let alldone = Object.values(subscriptions).every(filter => filter.done === true);
|
|
|
|
if (alldone) {
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Done", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
ws.close()
|
|
|
|
resolve(relay)
|
2024-01-28 12:43:23 -05:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
} else {
|
|
|
|
//console.log("Limit: ", { ...filters[0], until: lastSub1Event.created_at })
|
|
|
|
subscriptions[subscriptionId].counter = 0
|
2024-01-28 17:28:21 -05:00
|
|
|
let newFilter = { ...subscriptions[subscriptionId].filter }
|
|
|
|
newFilter.until = subscriptions[subscriptionId].lastEvent.created_at
|
|
|
|
ws.send(JSON.stringify(['REQ', subscriptions[subscriptionId].id, newFilter]))
|
2024-01-28 12:43:23 -05:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
if (msgType === 'AUTH') {
|
2024-01-28 14:27:08 -05:00
|
|
|
isAuthenticating = true
|
2024-01-28 12:50:44 -05:00
|
|
|
signNostrAuthEvent(relay, subscriptionId).then(
|
|
|
|
(event) => {
|
2024-01-28 14:14:31 -05:00
|
|
|
if (event) {
|
2024-01-28 14:33:03 -05:00
|
|
|
ws.send(JSON.stringify(['AUTH', event]))
|
2024-01-28 14:14:31 -05:00
|
|
|
} else {
|
2024-01-28 15:44:09 -05:00
|
|
|
updateRelayStatus(relay, "AUTH Req", 0, undefined, undefined, relayStatus)
|
2024-01-28 12:43:23 -05:00
|
|
|
ws.close()
|
|
|
|
reject(relay)
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
|
|
|
},
|
|
|
|
(reason) => {
|
2024-01-28 15:44:09 -05:00
|
|
|
updateRelayStatus(relay, "AUTH Req", 0, undefined, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
ws.close()
|
|
|
|
reject(relay)
|
|
|
|
},
|
|
|
|
)
|
2023-07-27 11:26:26 -04:00
|
|
|
}
|
2024-01-28 14:13:06 -05:00
|
|
|
|
2024-01-28 14:27:08 -05:00
|
|
|
if (msgType === 'CLOSED' && !isAuthenticating) {
|
2024-01-28 14:21:39 -05:00
|
|
|
subscriptions[subscriptionId].done = true
|
|
|
|
|
|
|
|
let alldone = Object.values(subscriptions).every(filter => filter.done === true);
|
|
|
|
if (alldone) {
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Done", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 14:21:39 -05:00
|
|
|
ws.close()
|
|
|
|
resolve(relay)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-28 14:13:06 -05:00
|
|
|
if (msgType === 'OK') {
|
2024-01-28 14:27:08 -05:00
|
|
|
isAuthenticating = false
|
2024-01-28 14:13:06 -05:00
|
|
|
// auth ok.
|
|
|
|
for (const [key, sub] of Object.entries(subscriptions)) {
|
|
|
|
ws.send(JSON.stringify(['REQ', sub.id, sub.filter]))
|
|
|
|
}
|
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
|
|
|
ws.onerror = (err) => {
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Done", 0, undefined, undefined, undefined, relayStatus)
|
2023-08-02 18:25:29 -04:00
|
|
|
try {
|
|
|
|
ws.close()
|
2024-01-28 12:50:44 -05:00
|
|
|
reject(relay)
|
|
|
|
} catch {
|
|
|
|
reject(relay)
|
2023-08-02 18:25:29 -04:00
|
|
|
}
|
2023-07-27 11:26:26 -04:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
ws.onclose = (socket, event) => {
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Done", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
resolve(relay)
|
|
|
|
}
|
|
|
|
} catch (exception) {
|
|
|
|
console.log(exception)
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Error", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
try {
|
|
|
|
ws.close()
|
|
|
|
} catch (exception) {
|
|
|
|
}
|
|
|
|
|
|
|
|
reject(relay)
|
|
|
|
}
|
|
|
|
})
|
2023-08-03 14:55:52 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// query relays for events published by this pubkey
|
2024-01-28 17:28:21 -05:00
|
|
|
const getEvents = async (filters, addedFilters, pubkey, relaySet) => {
|
2024-01-28 12:50:44 -05:00
|
|
|
// events hash
|
|
|
|
const events = {}
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// batch processing of 10 relays
|
2024-01-29 13:46:55 -05:00
|
|
|
await processInPool(relaySet, (relay, poolStatus) => fetchFromRelay(relay, filters, addedFilters, pubkey, events, poolStatus), 10, (progress) => $('#fetching-progress').val(progress))
|
2023-08-03 14:55:52 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
displayRelayStatus({})
|
2023-09-05 09:25:07 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// return data as an array of events
|
|
|
|
return Object.keys(events).map((id) => events[id])
|
|
|
|
}
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 15:59:28 -05:00
|
|
|
// broadcast events to list of relays
|
2024-01-29 13:46:55 -05:00
|
|
|
const broadcastEvents = async (data, relaySet) => {
|
|
|
|
const poolStatus = await processInPool(relaySet, (relay, poolStatus) => sendToRelay(relay, data, poolStatus), 10, (progress) => $('#broadcasting-progress').val(progress))
|
2024-01-28 15:59:28 -05:00
|
|
|
|
2024-01-29 13:46:55 -05:00
|
|
|
displayRelayStatus(poolStatus)
|
2024-01-28 15:59:28 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
const processInPool = async (items, processItem, poolSize, onProgress) => {
|
2024-01-28 12:50:44 -05:00
|
|
|
let pool = {};
|
|
|
|
let poolStatus = {}
|
|
|
|
let remaining = [...items]
|
|
|
|
|
|
|
|
while (remaining.length) {
|
|
|
|
let processing = remaining.splice(0, 1)
|
|
|
|
let item = processing[0]
|
|
|
|
pool[item] = processItem(item, poolStatus);
|
|
|
|
|
|
|
|
if (Object.keys(pool).length > poolSize - 1) {
|
|
|
|
try {
|
|
|
|
const resolvedId = await Promise.race(Object.values(pool)); // wait for one Promise to finish
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
delete pool[resolvedId]; // remove that Promise from the pool
|
|
|
|
} catch (resolvedId) {
|
|
|
|
delete pool[resolvedId]; // remove that Promise from the pool
|
|
|
|
}
|
2024-01-28 12:43:23 -05:00
|
|
|
}
|
|
|
|
|
2024-01-28 15:59:28 -05:00
|
|
|
onProgress(items.length - remaining.length)
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
await Promise.allSettled(Object.values(pool));
|
2024-01-28 17:28:21 -05:00
|
|
|
|
|
|
|
return poolStatus
|
2024-01-28 12:43:23 -05:00
|
|
|
}
|
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
const sendAllEvents = async (relay, data, relayStatus, ws) => {
|
|
|
|
console.log("Sending:", data.length, " events")
|
|
|
|
for (evnt of data) {
|
|
|
|
ws.send(JSON.stringify(['EVENT', evnt]))
|
2023-09-05 09:25:07 -04:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
2023-08-02 18:25:29 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// send events to a relay, returns a promisse
|
|
|
|
const sendToRelay = async (relay, data, relayStatus) =>
|
|
|
|
new Promise((resolve, reject) => {
|
|
|
|
try {
|
|
|
|
const ws = new WebSocket(relay)
|
2023-08-03 14:55:52 -04:00
|
|
|
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Starting", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
|
|
|
|
// prevent hanging forever
|
|
|
|
let myTimeout = setTimeout(() => {
|
|
|
|
ws.close()
|
|
|
|
reject('timeout')
|
|
|
|
}, 10_000)
|
|
|
|
|
|
|
|
// fetch events from relay
|
|
|
|
ws.onopen = () => {
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Sending", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
|
|
|
|
clearTimeout(myTimeout)
|
|
|
|
myTimeout = setTimeout(() => {
|
2023-08-02 18:25:29 -04:00
|
|
|
ws.close()
|
|
|
|
reject('timeout')
|
2023-08-03 14:55:52 -04:00
|
|
|
}, 10_000)
|
2023-08-02 18:25:29 -04:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
sendAllEvents(relay, data, relayStatus, ws)
|
|
|
|
}
|
|
|
|
// Listen for messages
|
|
|
|
ws.onmessage = (event) => {
|
|
|
|
clearTimeout(myTimeout)
|
|
|
|
myTimeout = setTimeout(() => {
|
|
|
|
ws.close()
|
|
|
|
reject('timeout')
|
|
|
|
}, 10_000)
|
2023-08-03 18:57:56 -04:00
|
|
|
|
2024-01-28 19:41:32 -05:00
|
|
|
const [msgType, subscriptionId, inserted, message] = JSON.parse(event.data)
|
2024-01-28 12:50:44 -05:00
|
|
|
// event messages
|
|
|
|
// end of subscription messages
|
|
|
|
if (msgType === 'OK') {
|
|
|
|
if (inserted == true) {
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, undefined, 1, undefined, undefined, message, relayStatus)
|
2023-09-05 09:25:07 -04:00
|
|
|
} else {
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, undefined, 0, undefined, undefined, message, relayStatus)
|
2024-01-28 19:42:51 -05:00
|
|
|
//console.log(relay, event.data)
|
2023-08-03 18:57:56 -04:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
} else {
|
|
|
|
console.log(relay, event.data)
|
2023-07-27 11:26:26 -04:00
|
|
|
}
|
2024-01-29 13:46:55 -05:00
|
|
|
|
|
|
|
if (msgType === 'AUTH') {
|
|
|
|
signNostrAuthEvent(relay, subscriptionId).then(
|
|
|
|
(event) => {
|
|
|
|
if (event) {
|
|
|
|
ws.send(JSON.stringify(['AUTH', event]))
|
|
|
|
} else {
|
|
|
|
updateRelayStatus(relay, "AUTH Req", 0, undefined, undefined, relayStatus)
|
|
|
|
ws.close()
|
|
|
|
reject(relay)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
(reason) => {
|
|
|
|
updateRelayStatus(relay, "AUTH Req", 0, undefined, undefined, relayStatus)
|
|
|
|
ws.close()
|
|
|
|
reject(relay)
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
}
|
|
|
|
ws.onerror = (err) => {
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Error", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
console.log("Error", err)
|
|
|
|
ws.close()
|
|
|
|
reject(err)
|
2023-07-27 11:26:26 -04:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
ws.onclose = (socket, event) => {
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Done", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
console.log("OnClose", relayStatus)
|
|
|
|
resolve()
|
|
|
|
}
|
|
|
|
} catch (exception) {
|
|
|
|
console.log(exception)
|
2024-01-28 19:41:32 -05:00
|
|
|
updateRelayStatus(relay, "Error", 0, undefined, undefined, undefined, relayStatus)
|
2024-01-28 12:50:44 -05:00
|
|
|
try {
|
|
|
|
ws.close()
|
|
|
|
} catch (exception) {
|
|
|
|
}
|
|
|
|
reject(exception)
|
2023-08-03 14:55:52 -04:00
|
|
|
}
|
2024-01-28 12:50:44 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
async function generateNostrEventId(msg) {
|
|
|
|
const digest = [
|
|
|
|
0,
|
|
|
|
msg.pubkey,
|
|
|
|
msg.created_at,
|
|
|
|
msg.kind,
|
|
|
|
msg.tags,
|
|
|
|
msg.content,
|
|
|
|
];
|
|
|
|
const digest_str = JSON.stringify(digest);
|
|
|
|
const hash = await sha256Hex(digest_str);
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
return hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
function sha256Hex(string) {
|
|
|
|
const utf8 = new TextEncoder().encode(string);
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
return crypto.subtle.digest('SHA-256', utf8).then((hashBuffer) => {
|
|
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
|
|
const hashHex = hashArray
|
|
|
|
.map((bytes) => bytes.toString(16).padStart(2, '0'))
|
|
|
|
.join('');
|
|
|
|
|
|
|
|
return hashHex;
|
|
|
|
});
|
|
|
|
}
|
2024-01-28 12:43:23 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
async function signNostrAuthEvent(relay, auth_challenge) {
|
|
|
|
try {
|
|
|
|
let msg = {
|
2024-01-28 14:28:59 -05:00
|
|
|
kind: 22242,
|
2024-01-28 12:50:44 -05:00
|
|
|
content: "",
|
|
|
|
tags: [
|
2024-01-28 14:01:42 -05:00
|
|
|
["relay", relay],
|
2024-01-28 12:50:44 -05:00
|
|
|
["challenge", auth_challenge]
|
|
|
|
],
|
|
|
|
};
|
2024-01-28 12:47:10 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// set msg fields
|
|
|
|
msg.created_at = Math.floor((new Date()).getTime() / 1000);
|
|
|
|
msg.pubkey = await window.nostr.getPublicKey();
|
2024-01-28 12:47:10 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// Generate event id
|
|
|
|
msg.id = await generateNostrEventId(msg);
|
2024-01-28 12:47:10 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
// Sign event
|
|
|
|
signed_msg = await window.nostr.signEvent(msg);
|
|
|
|
} catch (e) {
|
|
|
|
console.log("Failed to sign message with browser extension", e);
|
|
|
|
return undefined;
|
|
|
|
}
|
2024-01-28 12:47:10 -05:00
|
|
|
|
2024-01-28 12:50:44 -05:00
|
|
|
return signed_msg;
|
|
|
|
}
|