Skip to content

Commit 4ccc565

Browse files
committed
pool: hooks to notify when a relay fails to connect, then ask whether a connection should be attempted.
1 parent b3d3146 commit 4ccc565

File tree

2 files changed

+40
-2
lines changed

2 files changed

+40
-2
lines changed

abstract-pool.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { normalizeURL } from './utils.ts'
1111
import type { Event, EventTemplate, Nostr, VerifiedEvent } from './core.ts'
1212
import { type Filter } from './filter.ts'
1313
import { alwaysTrue } from './helpers.ts'
14+
import { Relay } from './relay.ts'
1415

1516
export type SubCloser = { close: (reason?: string) => void }
1617

@@ -19,6 +20,11 @@ export type AbstractPoolConstructorOptions = AbstractRelayConstructorOptions & {
1920
// in case that relay shouldn't be authenticated against
2021
// or a function to sign the AUTH event template otherwise (that function may still throw in case of failure)
2122
automaticallyAuth?: (relayURL: string) => null | ((event: EventTemplate) => Promise<VerifiedEvent>)
23+
// onRelayConnectionFailure is called with the URL of a relay that failed the initial connection
24+
onRelayConnectionFailure?: (url: string) => void
25+
// allowConnectingToRelay takes a relay URL and the operation being performed
26+
// return false to skip connecting to that relay
27+
allowConnectingToRelay?: (url: string, operation: ['read', Filter[]] | ['write', Event]) => boolean
2228
}
2329

2430
export type SubscribeManyParams = Omit<SubscriptionParams, 'onclose'> & {
@@ -40,6 +46,8 @@ export class AbstractSimplePool {
4046
public enableReconnect: boolean
4147
public automaticallyAuth?: (relayURL: string) => null | ((event: EventTemplate) => Promise<VerifiedEvent>)
4248
public trustedRelayURLs: Set<string> = new Set()
49+
public onRelayConnectionFailure?: (url: string) => void
50+
public allowConnectingToRelay?: (url: string, operation: ['read', Filter[]] | ['write', Event]) => boolean
4351

4452
private _WebSocket?: typeof WebSocket
4553

@@ -49,6 +57,8 @@ export class AbstractSimplePool {
4957
this.enablePing = opts.enablePing
5058
this.enableReconnect = opts.enableReconnect || false
5159
this.automaticallyAuth = opts.automaticallyAuth
60+
this.onRelayConnectionFailure = opts.onRelayConnectionFailure
61+
this.allowConnectingToRelay = opts.allowConnectingToRelay
5262
}
5363

5464
async ensureRelay(
@@ -181,13 +191,19 @@ export class AbstractSimplePool {
181191
// open a subscription in all given relays
182192
const allOpened = Promise.all(
183193
groupedRequests.map(async ({ url, filters }, i) => {
194+
if (this.allowConnectingToRelay?.(url, ['read', filters]) === false) {
195+
handleClose(i, 'connection skipped by allowConnectingToRelay')
196+
return
197+
}
198+
184199
let relay: AbstractRelay
185200
try {
186201
relay = await this.ensureRelay(url, {
187202
connectionTimeout: params.maxWait ? Math.max(params.maxWait * 0.8, params.maxWait - 1000) : undefined,
188203
abort: params.abort,
189204
})
190205
} catch (err) {
206+
this.onRelayConnectionFailure?.(url)
191207
handleClose(i, (err as any)?.message || String(err))
192208
return
193209
}
@@ -306,7 +322,18 @@ export class AbstractSimplePool {
306322
return Promise.reject('duplicate url')
307323
}
308324

309-
let r = await this.ensureRelay(url)
325+
if (this.allowConnectingToRelay?.(url, ['write', event]) === false) {
326+
return Promise.reject('connection skipped by allowConnectingToRelay')
327+
}
328+
329+
let r: Relay
330+
try {
331+
r = await this.ensureRelay(url)
332+
} catch (err) {
333+
this.onRelayConnectionFailure?.(url)
334+
return String('connection failure: ' + String(err))
335+
}
336+
310337
return r
311338
.publish(event)
312339
.catch(async err => {

abstract-relay.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,23 +145,34 @@ export class AbstractRelay {
145145
reject('connection timed out')
146146
this.connectionPromise = undefined
147147
this.onclose?.()
148-
this.closeAllSubscriptions('relay connection timed out')
148+
this.handleHardClose('relay connection timed out')
149149
}, opts.timeout)
150150
}
151151

152152
if (opts?.abort) {
153153
opts.abort.onabort = reject
154154
}
155155

156+
const connectionFailed = () => {
157+
clearTimeout(connectionTimeoutHandle)
158+
reject('connection failed')
159+
this.connectionPromise = undefined
160+
this.onclose?.()
161+
this.handleHardClose('relay connection failed')
162+
}
163+
156164
try {
157165
this.ws = new this._WebSocket(this.url)
166+
this.ws.addEventListener('error', connectionFailed)
158167
} catch (err) {
159168
clearTimeout(connectionTimeoutHandle)
160169
reject(err)
161170
return
162171
}
163172

164173
this.ws.onopen = () => {
174+
this.ws?.removeEventListener('error', connectionFailed)
175+
165176
if (this.reconnectTimeoutHandle) {
166177
clearTimeout(this.reconnectTimeoutHandle)
167178
this.reconnectTimeoutHandle = undefined

0 commit comments

Comments
 (0)