Nostryfied/js/nostr-utils.js

260 lines
8.2 KiB
JavaScript
Raw Normal View History

// from https://github.com/paulmillr/noble-secp256k1/blob/main/index.ts#L803
function hexToBytes(hex) {
if (typeof hex !== 'string') {
throw new TypeError('hexToBytes: expected string, got ' + typeof hex)
}
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
}
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')
}
}
// 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)
}
// parse inserted pubkey
const parsePubkey = (pubkey) =>
pubkey.match('npub1') ? npub2hexa(pubkey) : pubkey
// download js file
const downloadFile = (data, fileName) => {
const prettyJs = 'const data = ' + JSON.stringify(data, null, 2)
const tempLink = document.createElement('a')
const taBlob = new Blob([prettyJs], { type: 'text/javascript' })
tempLink.setAttribute('href', URL.createObjectURL(taBlob))
tempLink.setAttribute('download', fileName)
tempLink.click()
}
const updateRelayStatus = (relay, status, addToCount, relayStatusAndCount) => {
if (relayStatusAndCount[relay] == undefined) {
relayStatusAndCount[relay] = {}
}
if (status)
relayStatusAndCount[relay].status = status
if (relayStatusAndCount[relay].count != undefined)
relayStatusAndCount[relay].count = relayStatusAndCount[relay].count + addToCount
else
relayStatusAndCount[relay].count = addToCount
displayRelayStatus(relayStatusAndCount)
}
const displayRelayStatus = (relayStatusAndCount) => {
if (Object.keys(relayStatusAndCount).length > 0) {
let newText = Object.keys(relayStatusAndCount).map(
it => it.replace("wss://", "").replace("ws://", "") + ": " + relayStatusAndCount[it].status + " (" + relayStatusAndCount[it].count + ")"
).join("<br />")
$('#checking-relays').html(newText)
} else {
$('#checking-relays-header').html("")
$('#checking-relays').html("")
}
}
// fetch events from relay, returns a promise
const fetchFromRelay = async (relay, filters, pubkey, events, relayStatus) =>
new Promise((resolve, reject) => {
try {
updateRelayStatus(relay, "Starting", 0, relayStatus)
// open websocket
const ws = new WebSocket(relay)
2023-08-02 18:25:29 -04:00
// prevent hanging forever
let myTimeout = setTimeout(() => {
2023-08-02 18:25:29 -04:00
ws.close()
reject('timeout')
}, 10_000)
2023-08-02 18:25:29 -04:00
// subscription id
const subsId = 'my-sub'
// subscribe to events filtered by author
ws.onopen = () => {
clearTimeout(myTimeout)
myTimeout = setTimeout(() => {
ws.close()
reject('timeout')
}, 10_000)
updateRelayStatus(relay, "Downloading", 0, relayStatus)
ws.send(JSON.stringify(['REQ', subsId].concat(filters)))
}
// Listen for messages
ws.onmessage = (event) => {
const [msgType, subscriptionId, data] = JSON.parse(event.data)
// event messages
if (msgType === 'EVENT' && subscriptionId === subsId) {
clearTimeout(myTimeout)
myTimeout = setTimeout(() => {
ws.close()
reject('timeout')
2023-08-03 16:48:22 -04:00
}, 10_000)
const { id } = data
// don't save/reboradcast kind 3s that are not from the author.
// their are too big.
if (data.kind == 3 && data.pubkey != pubkey) {
return
}
updateRelayStatus(relay, undefined, 1, relayStatus)
// 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`)
}
// end of subscription messages
2023-08-02 18:25:29 -04:00
if (msgType === 'EOSE' && subscriptionId === subsId) {
updateRelayStatus(relay, "Done", 0, relayStatus)
2023-08-02 18:25:29 -04:00
ws.close()
resolve()
}
}
ws.onerror = (err) => {
updateRelayStatus(relay, "Done", 0, relayStatus)
2023-08-02 18:25:29 -04:00
ws.close()
reject(err)
}
ws.onclose = (socket, event) => {
updateRelayStatus(relay, "Done", 0, relayStatus)
2023-08-02 18:25:29 -04:00
resolve()
}
} catch (exception) {
console.log(exception)
updateRelayStatus(relay, "Error", 0, relayStatus)
2023-08-02 18:25:29 -04:00
try {
ws.close()
} catch (exception) {
}
reject(exception)
}
})
// query relays for events published by this pubkey
const getEvents = async (filters, pubkey) => {
// events hash
const events = {}
// batch processing of 10 relays
let fetchFunctions = [...relays]
while (fetchFunctions.length) {
let relaysForThisRound = fetchFunctions.splice(0, 10)
let relayStatus = {}
$('#fetching-progress').val(relays.length - fetchFunctions.length)
await Promise.allSettled( relaysForThisRound.map((relay) => fetchFromRelay(relay, filters, pubkey, events, relayStatus)) )
}
displayRelayStatus({})
// return data as an array of events
return Object.keys(events).map((id) => events[id])
}
// 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-02 18:25:29 -04:00
updateRelayStatus(relay, "Starting", 0, relayStatus)
2023-08-02 18:25:29 -04:00
// prevent hanging forever
let myTimeout = setTimeout(() => {
2023-08-02 18:25:29 -04:00
ws.close()
reject('timeout')
}, 10_000)
2023-08-02 18:25:29 -04:00
// fetch events from relay
ws.onopen = () => {
updateRelayStatus(relay, "Sending", 0, relayStatus)
for (evnt of data) {
clearTimeout(myTimeout)
myTimeout = setTimeout(() => {
ws.close()
reject('timeout')
}, 10_000)
ws.send(JSON.stringify(['EVENT', evnt]))
}
}
// Listen for messages
ws.onmessage = (event) => {
clearTimeout(myTimeout)
myTimeout = setTimeout(() => {
ws.close()
reject('timeout')
}, 10_000)
const [msgType, subscriptionId, inserted] = JSON.parse(event.data)
// event messages
// end of subscription messages
if (msgType === 'OK') {
if (inserted == true) {
updateRelayStatus(relay, undefined, 1, relayStatus)
} else {
console.log(event.data)
}
}
}
2023-08-02 18:25:29 -04:00
ws.onerror = (err) => {
updateRelayStatus(relay, "Error", 0, relayStatus)
2023-08-02 18:25:29 -04:00
console.log("Error", err)
ws.close()
2023-08-02 18:25:29 -04:00
reject(err)
}
ws.onclose = (socket, event) => {
updateRelayStatus(relay, "Done", 0, relayStatus)
resolve()
}
} catch (exception) {
console.log(exception)
updateRelayStatus(relay, "Error", 0, relayStatus)
try {
ws.close()
} catch (exception) {
}
reject(exception)
}
})
// broadcast events to list of relays
const broadcastEvents = async (data) => {
// batch processing of 10 relays
let broadcastFunctions = [...relays]
let relayStatus = {}
while (broadcastFunctions.length) {
let relaysForThisRound = broadcastFunctions.splice(0, 10)
$('#broadcasting-progress').val(relays.length - broadcastFunctions.length)
await Promise.allSettled( relaysForThisRound.map((relay) => sendToRelay(relay, data, relayStatus)) )
}
displayRelayStatus(relayStatus)
}