import 'websocket-polyfill' import {verifySignature} from './event' export function normalizeRelayURL(url) { let [host, ...qs] = url.split('?') if (host.slice(0, 4) === 'http') host = 'ws' + host.slice(4) if (host.slice(0, 2) !== 'ws') host = 'wss://' + host if (host.length && host[host.length - 1] === '/') host = host.slice(0, -1) return [host, ...qs].join('?') } export function relayConnect(url, onNotice) { url = normalizeRelayURL(url) var ws, resolveOpen, untilOpen, rejectOpen let attemptNumber = 1 function resetOpenState() { untilOpen = new Promise((resolve, reject) => { resolveOpen = resolve rejectOpen = reject }) } var channels = {} function connect() { ws = new WebSocket(url) ws.onopen = () => { console.log('connected to', url) resolveOpen() } ws.onerror = err => { console.log('error connecting to relay', url, err) rejectOpen() } ws.onclose = () => { resetOpenState() attemptNumber++ console.log( `relay ${url} connection closed. reconnecting in ${attemptNumber} seconds.` ) setTimeout(async () => { try { connect() } catch (err) {} }, attemptNumber * 1000) } ws.onmessage = async e => { var data try { data = JSON.parse(e.data) } catch (err) { data = e.data } if (data.length > 1) { if (data[0] === 'NOTICE') { if (data.length < 2) return console.log('message from relay ' + url + ': ' + data[1]) onNotice(data[1]) return } if (data[0] === 'EVENT') { if (data.length < 3) return let channel = data[1] let event = data[2] if (await verifySignature(event)) { if (channels[channel]) { channels[channel](event) } } else { console.warn('got event with invalid signature from ' + url, event) } return } } } } resetOpenState() try { connect() } catch (err) {} async function trySend(params) { let msg = JSON.stringify(params) if (ws && ws.readyState === WebSocket.OPEN) { ws.send(msg) } else { try { await untilOpen ws.send(msg) } catch (e) { console.log(`waiting to connect to ${url}`) } } } const sub = ({cb, filter}, channel = Math.random().toString().slice(2)) => { var filters = [] if (Array.isArray(filter)) { filters = filter } else { filters.push(filter) } trySend(['REQ', channel, ...filters]) channels[channel] = cb return { sub: ({cb = cb, filter = filter}) => sub({cb, filter}, channel), unsub: () => trySend(['CLOSE', channel]) } } return { url, sub, async publish(event, statusCallback = status => {}) { try { await trySend(['EVENT', event]) statusCallback(0) let {unsub} = relay.sub({ cb: () => { statusCallback(1) }, filter: {id: event.id} }) setTimeout(unsub, 5000) } catch (err) { statusCallback(-1) } }, close() { ws.close() }, get status() { return ws.readyState } } }