diff --git a/abstract-pool.ts b/abstract-pool.ts index ba6f8c7..4624097 100644 --- a/abstract-pool.ts +++ b/abstract-pool.ts @@ -11,6 +11,7 @@ import { normalizeURL } from './utils.ts' import type { Event, EventTemplate, Nostr, VerifiedEvent } from './core.ts' import { type Filter } from './filter.ts' import { alwaysTrue } from './helpers.ts' +import { Relay } from './relay.ts' export type SubCloser = { close: (reason?: string) => void } @@ -19,6 +20,11 @@ export type AbstractPoolConstructorOptions = AbstractRelayConstructorOptions & { // in case that relay shouldn't be authenticated against // or a function to sign the AUTH event template otherwise (that function may still throw in case of failure) automaticallyAuth?: (relayURL: string) => null | ((event: EventTemplate) => Promise) + // onRelayConnectionFailure is called with the URL of a relay that failed the initial connection + onRelayConnectionFailure?: (url: string) => void + // allowConnectingToRelay takes a relay URL and the operation being performed + // return false to skip connecting to that relay + allowConnectingToRelay?: (url: string, operation: ['read', Filter[]] | ['write', Event]) => boolean } export type SubscribeManyParams = Omit & { @@ -40,6 +46,8 @@ export class AbstractSimplePool { public enableReconnect: boolean public automaticallyAuth?: (relayURL: string) => null | ((event: EventTemplate) => Promise) public trustedRelayURLs: Set = new Set() + public onRelayConnectionFailure?: (url: string) => void + public allowConnectingToRelay?: (url: string, operation: ['read', Filter[]] | ['write', Event]) => boolean private _WebSocket?: typeof WebSocket @@ -49,6 +57,8 @@ export class AbstractSimplePool { this.enablePing = opts.enablePing this.enableReconnect = opts.enableReconnect || false this.automaticallyAuth = opts.automaticallyAuth + this.onRelayConnectionFailure = opts.onRelayConnectionFailure + this.allowConnectingToRelay = opts.allowConnectingToRelay } async ensureRelay( @@ -181,6 +191,11 @@ export class AbstractSimplePool { // open a subscription in all given relays const allOpened = Promise.all( groupedRequests.map(async ({ url, filters }, i) => { + if (this.allowConnectingToRelay?.(url, ['read', filters]) === false) { + handleClose(i, 'connection skipped by allowConnectingToRelay') + return + } + let relay: AbstractRelay try { relay = await this.ensureRelay(url, { @@ -188,6 +203,7 @@ export class AbstractSimplePool { abort: params.abort, }) } catch (err) { + this.onRelayConnectionFailure?.(url) handleClose(i, (err as any)?.message || String(err)) return } @@ -306,7 +322,18 @@ export class AbstractSimplePool { return Promise.reject('duplicate url') } - let r = await this.ensureRelay(url) + if (this.allowConnectingToRelay?.(url, ['write', event]) === false) { + return Promise.reject('connection skipped by allowConnectingToRelay') + } + + let r: Relay + try { + r = await this.ensureRelay(url) + } catch (err) { + this.onRelayConnectionFailure?.(url) + return String('connection failure: ' + String(err)) + } + return r .publish(event) .catch(async err => { diff --git a/abstract-relay.ts b/abstract-relay.ts index ba3e490..b3bc3c7 100644 --- a/abstract-relay.ts +++ b/abstract-relay.ts @@ -145,7 +145,7 @@ export class AbstractRelay { reject('connection timed out') this.connectionPromise = undefined this.onclose?.() - this.closeAllSubscriptions('relay connection timed out') + this.handleHardClose('relay connection timed out') }, opts.timeout) } @@ -153,8 +153,17 @@ export class AbstractRelay { opts.abort.onabort = reject } + const connectionFailed = () => { + clearTimeout(connectionTimeoutHandle) + reject('connection failed') + this.connectionPromise = undefined + this.onclose?.() + this.handleHardClose('relay connection failed') + } + try { this.ws = new this._WebSocket(this.url) + this.ws.addEventListener('error', connectionFailed) } catch (err) { clearTimeout(connectionTimeoutHandle) reject(err) @@ -162,6 +171,8 @@ export class AbstractRelay { } this.ws.onopen = () => { + this.ws?.removeEventListener('error', connectionFailed) + if (this.reconnectTimeoutHandle) { clearTimeout(this.reconnectTimeoutHandle) this.reconnectTimeoutHandle = undefined diff --git a/jsr.json b/jsr.json index c42bcd1..4808d42 100644 --- a/jsr.json +++ b/jsr.json @@ -1,6 +1,6 @@ { "name": "@nostr/tools", - "version": "2.22.0", + "version": "2.22.1", "exports": { ".": "./index.ts", "./core": "./core.ts", diff --git a/package.json b/package.json index 5495c89..f272da1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "nostr-tools", - "version": "2.22.0", + "version": "2.22.1", "description": "Tools for making a Nostr client.", "repository": { "type": "git",