(event: Event)
| 268 | } |
| 269 | |
| 270 | protected async isRateLimited(event: Event): Promise<boolean> { |
| 271 | if (this.getRelayPublicKey() === event.pubkey) { |
| 272 | return false |
| 273 | } |
| 274 | |
| 275 | const { whitelists, rateLimits } = this.settings().limits?.event ?? {} |
| 276 | if (!rateLimits || !rateLimits.length) { |
| 277 | return false |
| 278 | } |
| 279 | |
| 280 | if ( |
| 281 | typeof whitelists?.pubkeys !== 'undefined' && |
| 282 | Array.isArray(whitelists?.pubkeys) && |
| 283 | whitelists.pubkeys.includes(event.pubkey) |
| 284 | ) { |
| 285 | return false |
| 286 | } |
| 287 | |
| 288 | if ( |
| 289 | typeof whitelists?.ipAddresses !== 'undefined' && |
| 290 | Array.isArray(whitelists?.ipAddresses) && |
| 291 | whitelists.ipAddresses.includes(this.webSocket.getClientAddress()) |
| 292 | ) { |
| 293 | return false |
| 294 | } |
| 295 | |
| 296 | const rateLimiter = this.rateLimiter() |
| 297 | |
| 298 | const toString = (input: any | any[]): string => { |
| 299 | return Array.isArray(input) ? `[${input.map(toString)}]` : input.toString() |
| 300 | } |
| 301 | |
| 302 | const hit = ({ period, rate, kinds = undefined }: EventRateLimit) => { |
| 303 | const key = Array.isArray(kinds) |
| 304 | ? `${event.pubkey}:events:${period}:${toString(kinds)}` |
| 305 | : `${event.pubkey}:events:${period}` |
| 306 | |
| 307 | return rateLimiter.hit(key, 1, { period, rate }) |
| 308 | } |
| 309 | |
| 310 | let limited = false |
| 311 | for (const { rate, period, kinds } of rateLimits) { |
| 312 | // skip if event kind does not apply |
| 313 | if (Array.isArray(kinds) && !kinds.some(isEventKindOrRangeMatch(event))) { |
| 314 | continue |
| 315 | } |
| 316 | |
| 317 | const isRateLimited = await hit({ period, rate, kinds }) |
| 318 | |
| 319 | if (isRateLimited) { |
| 320 | logger('rate limited %s: %d events / %d ms exceeded', event.pubkey, rate, period) |
| 321 | |
| 322 | limited = true |
| 323 | } |
| 324 | } |
| 325 | |
| 326 | return limited |
| 327 | } |
no test coverage detected