diff --git a/abstract-pool.ts b/abstract-pool.ts index 9a78570..6fab8ea 100644 --- a/abstract-pool.ts +++ b/abstract-pool.ts @@ -266,7 +266,7 @@ export class AbstractSimplePool { subscribeEose( relays: string[], filter: Filter, - params: Pick, + params: Pick, ): SubCloser { let subcloser: SubCloser subcloser = this.subscribe(relays, filter, { @@ -283,7 +283,7 @@ export class AbstractSimplePool { subscribeManyEose( relays: string[], filter: Filter, - params: Pick, + params: Pick, ): SubCloser { return this.subscribeEose(relays, filter, params) } diff --git a/abstract-relay.ts b/abstract-relay.ts index bc598c6..21e26b4 100644 --- a/abstract-relay.ts +++ b/abstract-relay.ts @@ -426,6 +426,8 @@ export class AbstractRelay { const event = data[2] as NostrEvent if (this.verifyEvent(event) && matchFilters(so.filters, event)) { so.onevent(event) + } else { + so.oninvalidevent?.(event) } if (!so.lastEmitted || so.lastEmitted < event.created_at) so.lastEmitted = event.created_at return @@ -504,6 +506,7 @@ export class Subscription { public receivedEvent: ((relay: AbstractRelay, id: string) => void) | undefined public onevent: (evt: Event) => void + public oninvalidevent: ((evt: unknown) => void) | undefined public oneose: (() => void) | undefined public onclose: ((reason: string) => void) | undefined @@ -525,6 +528,7 @@ export class Subscription { this.oneose = params.oneose this.onclose = params.onclose + this.oninvalidevent = params.oninvalidevent this.onevent = params.onevent || (event => { @@ -576,6 +580,7 @@ export class Subscription { export type SubscriptionParams = { onevent?: (evt: Event) => void + oninvalidevent?: (evt: unknown) => void oneose?: () => void onclose?: (reason: string) => void alreadyHaveEvent?: (id: string) => boolean diff --git a/pool.test.ts b/pool.test.ts index 543d161..47cfe86 100644 --- a/pool.test.ts +++ b/pool.test.ts @@ -393,3 +393,24 @@ test('track relays when publishing', async () => { await Promise.all(pool.publish(relayURLs, event2)) expect(pool.seenOn.get(event2.id)).toBeUndefined() }) + +test('oninvalidevent is called through the pool for invalid events', async done => { + const mockRelay = mockRelays[0] + const relay = await pool.ensureRelay(mockRelay.url) + + const sub = relay.prepareSubscription([{ kinds: [1] }], { + oninvalidevent(event) { + expect((event as any).kind).toBe('1') + sub.close() + done() + }, + }) + + const sk = generateSecretKey() + const wrongFieldTypeEvent = [finalizeEvent( + { kind: 1, content: 'hello', created_at: Math.floor(Date.now() / 1000), tags: [] }, + sk, + )].map(v => { (v as any).kind = '1'; return v })[0] + + relay._onmessage({ data: JSON.stringify(['EVENT', sub.id, wrongFieldTypeEvent]) } as MessageEvent) +}) diff --git a/relay.test.ts b/relay.test.ts index c08c0b0..81d77b7 100644 --- a/relay.test.ts +++ b/relay.test.ts @@ -1,6 +1,7 @@ import { expect, test } from 'bun:test' import { Server } from 'mock-socket' import { finalizeEvent, generateSecretKey, getPublicKey } from './pure.ts' +import { NostrEvent } from './pure.ts' import { Relay, useWebSocketImplementation } from './relay.ts' import { MockRelay, MockWebSocketClient } from './test-helpers.ts' @@ -336,3 +337,59 @@ test('reconnect on disconnect', async () => { expect(relay.connected).toBeTrue() expect(closes).toBe(1) // should not have closed again }) + +test('oninvalidevent is called for malformed events', async done => { + const mockRelay = new MockRelay() + const relay = new Relay(mockRelay.url) + await relay.connect() + + const sub = relay.prepareSubscription([{ kinds: [1] }], { + oninvalidevent(event) { + expect((event as any).kind).toBe('1') + sub.close() + relay.close() + done() + }, + }) + + const sk = generateSecretKey() + const wrongFieldTypeEvent = [finalizeEvent( + { + kind: 1, + content: 'content', + created_at: 0, + tags: [], + }, + sk + )].map(v => { (v as any).kind = '1'; return v })[0] + + relay._onmessage({ data: JSON.stringify(['EVENT', sub.id, wrongFieldTypeEvent]) } as MessageEvent) +}) + +test('oninvalidevent is called for events that do not match subscription filters', async done => { + const mockRelay = new MockRelay() + const sk = generateSecretKey() + const relay = new Relay(mockRelay.url) + await relay.connect() + + const sub = relay.prepareSubscription([{ kinds: [999] }], { + oninvalidevent(event) { + expect((event as NostrEvent).kind).toBe(1) + sub.close() + relay.close() + done() + }, + }) + + const event = finalizeEvent( + { + kind: 1, + content: 'does not match filter', + created_at: Math.floor(Date.now() / 1000), + tags: [], + }, + sk, + ) + + relay._onmessage({ data: JSON.stringify(['EVENT', sub.id, event]) } as MessageEvent) +})