From 32793146a41c192299ae11f18ffdea38c2e7df25 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Tue, 14 Feb 2023 11:24:06 -0300 Subject: [PATCH] remove `untilOpen` promise that was causing memory leaks when a connection was never opened. --- magic.ts | 140 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- relay.ts | 6 --- 3 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 magic.ts diff --git a/magic.ts b/magic.ts new file mode 100644 index 0000000..d0b5cce --- /dev/null +++ b/magic.ts @@ -0,0 +1,140 @@ +import {Relay, relayInit} from './relay' +import {Event} from './event' +import {normalizeURL} from './utils' + +export default function ( + writeableRelays: string[], + fallbackRelays: string[], + safeRelays: string[] +) { + return new MagicPool(fallbackRelays, writeableRelays, safeRelays) +} + +class MagicPool { + private _conn: {[url: string]: Relay} + private _fallback: {[url: string]: Relay} + private _write: {[url: string]: Relay} + private _safe: {[url: string]: Relay} + + private _profileRelays: {[pubkey: string]: RelayTableScore} + private _tempCache: {[id: string]: Event} + + constructor( + fallbackRelays: string[], + writeableRelays: string[], + safeRelays: string[] = [ + 'wss://eden.nostr.land', + 'wss://nostr.milou.lol', + 'wss://relay.minds.com/nostr/v1/ws' + ] + ) { + this._conn = {} + this._write = {} + this._fallback = {} + this._profileRelays = {} + this._tempCache = {} + + const hasEventId = (id: string): boolean => id in this._tempCache + const init = (url: string) => { + this._conn[normalizeURL(url)] = relayInit(normalizeURL(url), hasEventId) + } + + fallbackRelays.forEach(init) + writeableRelays.forEach(init) + safeRelays.forEach(init) + + this._write = Object.fromEntries( + writeableRelays.map(url => [ + normalizeURL(url), + this._conn[normalizeURL(url)] + ]) + ) + this._fallback = Object.fromEntries( + fallbackRelays.map(url => [ + normalizeURL(url), + this._conn[normalizeURL(url)] + ]) + ) + this._safe = Object.fromEntries( + safeRelays.map(url => [normalizeURL(url), this._conn[normalizeURL(url)]]) + ) + } + + publish(event: Event) { + return Promise.all( + Object.entries(this._write).map( + ([url, relay]) => + new Promise(async resolve => { + await relay.connect() + let pub = relay.publish(event) + let to = setTimeout(() => { + let end = setTimeout(() => { + resolve({url, success: false, reason: 'timeout'}) + }, 2500) + pub.on('seen', () => { + clearTimeout(end) + resolve({url, success: true, reason: 'seen'}) + }) + }, 2500) + pub.on('ok', () => { + clearTimeout(to) + resolve({url, success: true, reason: 'ok'}) + }) + pub.on('failed', (reason: string) => { + clearTimeout(to) + resolve({url, success: false, reason}) + }) + }) + ) + ) + } + + profile( + pubkey: string, + onUpdate: (events: Event[]) => void + ): { + page(n: number): void + } { + var relays = new Set() + let rts = this._profileRelays[pubkey] + if (rts) { + relays = rts.get(3) + } + + let fallback = Object.values(this._fallback) + for (let i = 0; i < fallback.length; i++) { + if (relays.size < 3) { + relays.add(fallback[Math.floor(Math.random() * fallback.length)]) + } else break + } + + // start subscription + for (let r in relays) { + r. + } + + return { + page(n: number) {} + } + } +} + +class RelayTableScore { + seen: string[] = [] + hinted: string[] = [] + explicit: string[] = [] + + get(n: number): Set { + let relays = new Set() + for (let i = 0; i < n; i++) { + for (let j = 0; j < 3; j++) { + let v = [this.seen, this.explicit, this.hinted][j][i] + if (v) { + relays.add(v) + if (relays.size >= n) return relays + } + } + } + return relays + } +} diff --git a/package.json b/package.json index 5d23d3e..73be3b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "nostr-tools", - "version": "1.4.0", + "version": "1.4.1", "description": "Tools for making a Nostr client.", "repository": { "type": "git", diff --git a/relay.ts b/relay.ts index 7593cef..0073276 100644 --- a/relay.ts +++ b/relay.ts @@ -38,10 +38,6 @@ export type SubscriptionOptions = { export function relayInit(url: string): Relay { var ws: WebSocket var resolveClose: () => void - var setOpen: (value: PromiseLike | void) => void - var untilOpen = new Promise(resolve => { - setOpen = resolve - }) var openSubs: {[id: string]: {filters: Filter[]} & SubscriptionOptions} = {} var listeners: { connect: Array<() => void> @@ -74,7 +70,6 @@ export function relayInit(url: string): Relay { ws.onopen = () => { listeners.connect.forEach(cb => cb()) - setOpen() resolve() } ws.onerror = () => { @@ -171,7 +166,6 @@ export function relayInit(url: string): Relay { async function trySend(params: [string, ...any]) { let msg = JSON.stringify(params) - await untilOpen try { ws.send(msg) } catch (err) {