nostr-relaypool
Version:
A Nostr RelayPool implementation in TypeScript using only nostr-tools library as a dependency.
249 lines (221 loc) • 5.89 kB
text/typescript
/* eslint-env jest */
import {
signEvent,
generatePrivateKey,
getEventHash,
getPublicKey,
type Event,
} from "nostr-tools";
import type {Relay} from "./relay";
import {relayInit} from "./relay";
import {InMemoryRelayServer} from "./in-memory-relay-server";
import WebSocket from "ws";
let relay: Relay;
let _relayServer: InMemoryRelayServer;
beforeAll(() => {
_relayServer = new InMemoryRelayServer(8089);
});
beforeEach(async () => {
relay = relayInit("ws://localhost:8089/", undefined, true);
relay.connect();
_relayServer.clear();
});
afterEach(async () => {
await relay.close();
_relayServer.clear();
});
afterAll(async () => {
await _relayServer.close();
});
test("connectivity", () => {
return expect(
new Promise((resolve) => {
relay.on("connect", () => {
resolve(true);
});
relay.on("error", () => {
resolve(false);
});
})
).resolves.toBe(true);
});
async function publishAndGetEvent(
options: {content?: string} = {}
): Promise<Event> {
const sk = generatePrivateKey();
const pk = getPublicKey(sk);
const unsignedEvent = {
kind: 27572,
pubkey: pk,
created_at: Math.floor(Date.now() / 1000),
tags: [],
content: options.content || "nostr-tools test suite",
};
const eventId = getEventHash(unsignedEvent);
const event: Event = {
sig: signEvent(unsignedEvent, sk),
id: eventId,
...unsignedEvent,
};
// console.log("publishing event", event);
relay.publish(event);
return new Promise((resolve) =>
relay
// @ts-ignore
.sub([{ids: [event.id]}])
.on("event", (event: Event) => {
resolve(event);
})
);
}
test("querying", async () => {
const event: Event & {id: string} = await publishAndGetEvent();
var resolve1: (success: boolean) => void;
var resolve2: (success: boolean) => void;
const promiseAll = Promise.all([
new Promise((resolve) => {
resolve(true);
resolve1 = resolve;
}),
new Promise((resolve) => {
resolve2 = resolve;
resolve(true);
}),
]);
const sub = relay.sub([
{
kinds: [event.kind],
},
]);
sub.on("event", (event: Event) => {
expect(event).toHaveProperty("id", event.id);
resolve1(true);
});
sub.on("eose", () => {
resolve2(true);
});
return expect(promiseAll).resolves.toEqual([true, true]);
});
test("listening (twice) and publishing", async () => {
const sk = generatePrivateKey();
const pk = getPublicKey(sk);
var resolve1: (success: boolean) => void;
var resolve2: (success: boolean) => void;
const sub = relay.sub([
{
kinds: [27572],
authors: [pk],
},
]);
sub.on("event", (event: Event) => {
expect(event).toHaveProperty("pubkey", pk);
expect(event).toHaveProperty("kind", 27572);
expect(event).toHaveProperty("content", "nostr-tools test suite");
resolve1(true);
});
sub.on("event", (event: Event) => {
expect(event).toHaveProperty("pubkey", pk);
expect(event).toHaveProperty("kind", 27572);
expect(event).toHaveProperty("content", "nostr-tools test suite");
resolve2(true);
});
const event = {
kind: 27572,
pubkey: pk,
created_at: Math.floor(Date.now() / 1000),
tags: [],
content: "nostr-tools test suite",
};
// @ts-ignore
event.id = getEventHash(event);
// @ts-ignore
event.sig = signEvent(event, sk);
// @ts-ignore
relay.publish(event);
return expect(
Promise.all([
new Promise((resolve) => {
resolve1 = resolve;
}),
new Promise((resolve) => {
resolve2 = resolve;
}),
])
).resolves.toEqual([true, true]);
});
test("two subscriptions", async () => {
const sk = generatePrivateKey();
const pk = getPublicKey(sk);
const event = {
kind: 27572,
pubkey: pk,
created_at: Math.floor(Date.now() / 1000),
tags: [],
content: "nostr-tools test suite",
};
// @ts-ignore
event.id = getEventHash(event);
// @ts-ignore
event.sig = signEvent(event, sk);
await expect(
new Promise((resolve) => {
const sub = relay.sub([
{
kinds: [27572],
authors: [pk],
},
]);
sub.on("event", (event: Event) => {
expect(event).toHaveProperty("pubkey", pk);
expect(event).toHaveProperty("kind", 27572);
expect(event).toHaveProperty("content", "nostr-tools test suite");
resolve(true);
});
// @ts-ignore
relay.publish(event);
})
).resolves.toEqual(true);
await expect(
new Promise((resolve) => {
const sub = relay.sub([
{
kinds: [27572],
authors: [pk],
},
]);
sub.on("event", (event: Event) => {
expect(event).toHaveProperty("pubkey", pk);
expect(event).toHaveProperty("kind", 27572);
expect(event).toHaveProperty("content", "nostr-tools test suite");
resolve(true);
});
// @ts-ignore
relay.publish(event);
})
).resolves.toEqual(true);
});
test("autoreconnect", async () => {
expect(relay.status).toBe(WebSocket.CONNECTING);
await publishAndGetEvent();
expect(relay.status).toBe(WebSocket.OPEN);
_relayServer.disconnectAll();
await new Promise((resolve) => setTimeout(resolve, 0));
expect(relay.status).toBeGreaterThanOrEqual(WebSocket.CLOSING);
await publishAndGetEvent();
});
// jest -t 'relay memory' --testTimeout 1000000 --logHeapUsage
// PASS ./relay.test.ts (93.914 s, 480 MB heap size)
test.skip("relay memory usage", async () => {
// @ts-ignore
relay.relay.logging = false;
await publishAndGetEvent({content: "x".repeat(20 * 1024 * 1024)});
for (let i = 0; i < 300; i++) {
await new Promise((resolve) => {
const sub = relay.sub([{}]);
sub.on("event", (event: Event) => {
sub.unsub();
resolve(true);
});
});
}
});