diff --git a/packages/net2/__tests__/request.test.ts b/packages/net2/__tests__/request.test.ts new file mode 100644 index 0000000..d84f014 --- /dev/null +++ b/packages/net2/__tests__/request.test.ts @@ -0,0 +1,141 @@ +import { describe, expect, it, vi, beforeEach, afterEach } from "vitest" +import { Nip01Signer } from '@welshman/signer' +import { LOCAL_RELAY_URL, makeEvent } from '@welshman/util' +import { ClientMessageType } from "../src/message" +import { AdapterContext, AbstractAdapter, AdapterEventType } from "../src/adapter" +import { unireq, multireq, RequestEventType } from "../src/request" +import { Tracker } from "../src/tracker" + +class MockAdapter extends AbstractAdapter { + constructor(readonly send) { + super() + + this.sockets = [] + this.urls = [LOCAL_RELAY_URL] + } + + receive = (message: RelayMessage) => { + this.emit(AdapterEventType.Receive, message, this.url) + } +} + +describe("Unireq", () => { + beforeEach(() => { + vi.useFakeTimers() + }) + + it("everything basically works", async () => { + const sendSpy = vi.fn() + const adapter = new MockAdapter(sendSpy) + const req = unireq({ + relay: 'whatever', + filter: {kinds: [1]}, + context: {getAdapter: () => adapter}, + autoClose: true, + }) + + const duplicateSpy = vi.fn() + const invalidSpy = vi.fn() + const filteredSpy = vi.fn() + const eventSpy = vi.fn() + const eoseSpy = vi.fn() + const closeSpy = vi.fn() + + req.on(RequestEventType.Duplicate, duplicateSpy) + req.on(RequestEventType.Invalid, invalidSpy) + req.on(RequestEventType.Filtered, filteredSpy) + req.on(RequestEventType.Event, eventSpy) + req.on(RequestEventType.Eose, eoseSpy) + req.on(RequestEventType.Close, closeSpy) + + await vi.runAllTimers() + + expect(sendSpy).toHaveBeenCalledWith([ClientMessageType.Req, req._id, {kinds: [1]}]) + + const signer = Nip01Signer.ephemeral() + const event1 = await signer.sign(makeEvent(1)) + const event2 = await signer.sign(makeEvent(7)) + const event3 = makeEvent(1) + + adapter.receive(["EVENT", req._id, event1]) + adapter.receive(["EVENT", req._id, event2]) + adapter.receive(["EVENT", req._id, event1]) + adapter.receive(["EVENT", req._id, event3]) + + await vi.runAllTimers() + + expect(duplicateSpy).toHaveBeenCalledWith(event1) + expect(filteredSpy).toHaveBeenCalledWith(event2) + expect(invalidSpy).toHaveBeenCalledWith(event3) + expect(eventSpy).toHaveBeenCalledWith(event1) + expect(eoseSpy).toHaveBeenCalledTimes(0) + + adapter.receive(["EOSE", req._id]) + + expect(eoseSpy).toHaveBeenCalledTimes(1) + expect(closeSpy).toHaveBeenCalledTimes(1) + }) +}) + +describe("Multireq", () => { + it("everything basically works", async () => { + const send1Spy = vi.fn() + const adapter1 = new MockAdapter(send1Spy) + const send2Spy = vi.fn() + const adapter2 = new MockAdapter(send2Spy) + const req = multireq({ + autoClose: true, + relays: ['1', '2'], + filter: {kinds: [1]}, + context: { + getAdapter: (url: string) => url === '1' ? adapter1 : adapter2 + }, + }) + + const duplicateSpy = vi.fn() + const invalidSpy = vi.fn() + const filteredSpy = vi.fn() + const eventSpy = vi.fn() + const eoseSpy = vi.fn() + const closeSpy = vi.fn() + + req.on(RequestEventType.Duplicate, duplicateSpy) + req.on(RequestEventType.Invalid, invalidSpy) + req.on(RequestEventType.Filtered, filteredSpy) + req.on(RequestEventType.Event, eventSpy) + req.on(RequestEventType.Eose, eoseSpy) + req.on(RequestEventType.Close, closeSpy) + + await vi.runAllTimers() + + expect(send1Spy).toHaveBeenCalledWith([ClientMessageType.Req, req._children[0]._id, {kinds: [1]}]) + expect(send2Spy).toHaveBeenCalledWith([ClientMessageType.Req, req._children[1]._id, {kinds: [1]}]) + + const signer = Nip01Signer.ephemeral() + const event1 = await signer.sign(makeEvent(1)) + const event2 = await signer.sign(makeEvent(7)) + const event3 = makeEvent(1) + const event4 = await signer.sign(makeEvent(1)) + + adapter1.receive(["EVENT", req._children[0]._id, event1]) + adapter1.receive(["EVENT", req._children[0]._id, event2]) + adapter1.receive(["EVENT", req._children[0]._id, event3]) + adapter2.receive(["EVENT", req._children[1]._id, event1]) + adapter2.receive(["EVENT", req._children[1]._id, event4]) + + await vi.runAllTimers() + + expect(duplicateSpy).toHaveBeenCalledWith(event1, '2') + expect(filteredSpy).toHaveBeenCalledWith(event2, '1') + expect(invalidSpy).toHaveBeenCalledWith(event3, '1') + expect(eventSpy).toHaveBeenCalledWith(event1, '1') + expect(eoseSpy).toHaveBeenCalledTimes(0) + + adapter1.receive(["EOSE", req._children[0]._id]) + adapter2.receive(["EOSE", req._children[1]._id]) + + expect(eoseSpy).toHaveBeenCalledTimes(2) + expect(closeSpy).toHaveBeenCalledTimes(1) + }) +}) + diff --git a/packages/net2/src/request.ts b/packages/net2/src/request.ts index c738a1a..532cf8c 100644 --- a/packages/net2/src/request.ts +++ b/packages/net2/src/request.ts @@ -1,4 +1,5 @@ import {EventEmitter} from "events" +import {verifyEvent as nostrToolsVerifyEvent} from 'nostr-tools' import {on, call, randomId, yieldThread} from "@welshman/lib" import {Filter, matchFilter, SignedEvent} from "@welshman/util" import {RelayMessage, ClientMessageType, isRelayEvent, isRelayEose} from "./message.js" @@ -7,6 +8,14 @@ import {SocketEventType, SocketStatus} from "./socket.js" import {TypedEmitter, Unsubscriber} from "./util.js" import {Tracker} from "./tracker.js" +export const defaultVerifyEvent = (event: SignedEvent) => { + try { + return nostrToolsVerifyEvent(event) + } catch (e) { + return false + } +} + export enum RequestEventType { Close = "request:event:close", Disconnect = "request:event:disconnect", @@ -48,6 +57,10 @@ export class Unireq extends (EventEmitter as new () => TypedEmitter TypedEmitter TypedEmitter { this.emit(RequestEventType.Event, event, relay)