Skip to content

Commit

Permalink
merge with all-your-relays-are-mine
Browse files Browse the repository at this point in the history
  • Loading branch information
pablof7z committed Jul 22, 2024
2 parents f7eaec3 + 8185295 commit 8ad0be0
Show file tree
Hide file tree
Showing 8 changed files with 604 additions and 143 deletions.
5 changes: 3 additions & 2 deletions ndk/src/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { decrypt, encrypt } from "./nip04.js";
import { encode } from "./nip19.js";
import { repost } from "./repost.js";
import { fetchReplyEvent, fetchRootEvent, fetchTaggedEvent } from "./fetch-tagged-event.js";
import { NDKEventSerialized, deserialize, serialize } from "./serializer.js";
import { type NDKEventSerialized, deserialize, serialize } from "./serializer.js";
import { validate, verifySignature, getEventHash } from "./validation.js";
import { matchFilter } from "nostr-tools";

Expand Down Expand Up @@ -352,7 +352,8 @@ export class NDKEvent extends EventEmitter {

if (!relaySet) {
// If we have a devWriteRelaySet, use it to publish all events
relaySet = this.ndk.devWriteRelaySet || await calculateRelaySetFromEvent(this.ndk, this);
relaySet =
this.ndk.devWriteRelaySet || (await calculateRelaySetFromEvent(this.ndk, this));
}

// If the published event is a delete event, notify the cache if there is one
Expand Down
7 changes: 4 additions & 3 deletions ndk/src/events/nip19.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,22 @@ describe("NDKEvent", () => {

const a = event.encode();
expect(a).toBe(
"naddr1qvzqqqr4xqpzp75cf0tahv5z7plpdeaws7ex52nmnwgtwfr2g3m37r844evqrr6jqyf8wumn8ghj7un9d3shjtnxxaazu6t0qqzrzv3nxsrcfx9f"
"naddr1qvzqqqr4xqpzp75cf0tahv5z7plpdeaws7ex52nmnwgtwfr2g3m37r844evqrr6jqyfhwumn8ghj7un9d3shjtnxxaazu6t09uqqgvfjxv6qrvzzck"
);
});

it("encodes events as notes when the relay is known", () => {
const event = new NDKEvent(ndk, {
kind: 1,
content: "hello world",
pubkey: "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52",
tags: [["d", "1234"]],
tags: [["e", "1234"]],
} as NostrEvent);
event.relay = new NDKRelay("wss://relay.f7z.io/");

const a = event.encode();
expect(a).toBe(
"nevent1qgs04xzt6ldm9qhs0ctw0t58kf4z57umjzmjg6jywu0seadwtqqc75spzfmhxue69uhhyetvv9ujue3h0ghxjmcqqqwvqq2y"
"nevent1qgs04xzt6ldm9qhs0ctw0t58kf4z57umjzmjg6jywu0seadwtqqc75spzdmhxue69uhhyetvv9ujue3h0ghxjme0qqqqcmeuul"
);
});
});
Expand Down
13 changes: 8 additions & 5 deletions ndk/src/relay/auth-policies.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { NDKRelay } from ".";
import type { NDKRelay } from ".";
import { NDKEvent } from "../events";
import { NDKKind } from "../events/kinds";
import { NDK } from "../ndk";
import { NDKSigner } from "../signers";
import { NDKPool } from "./pool";
import type { NDK } from "../ndk";
import type { NDKSigner } from "../signers";
import type { NDKPool } from "./pool";
import createDebug from "debug";

/**
Expand Down Expand Up @@ -52,7 +52,9 @@ async function signAndAuth(
}

/**
* Uses the signer to sign an event and then authenticate with the relay. If no signer is provided the NDK signer will be used. If none is not available it will wait for one to be ready.
* Uses the signer to sign an event and then authenticate with the relay.
* If no signer is provided the NDK signer will be used.
* If none is not available it will wait for one to be ready.
*/
function signIn({ ndk, signer, debug }: ISignIn = {}) {
debug ??= createDebug("ndk:auth-policies:signIn");
Expand All @@ -70,6 +72,7 @@ function signIn({ ndk, signer, debug }: ISignIn = {}) {
signer ??= ndk?.signer;

// If we dont have a signer, we need to wait for one to be ready
// eslint-disable-next-line no-async-promise-executor
return new Promise(async (resolve, reject) => {
if (signer) {
await signAndAuth(event, relay, signer, debug!, resolve, reject);
Expand Down
139 changes: 97 additions & 42 deletions ndk/src/relay/connectivity.test.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,122 @@
import { NDKRelayConnectivity } from "./connectivity.js";
import { NDKRelay, NDKRelayStatus } from "./index.js";
import { NDKRelayConnectivity } from "./connectivity";
import { NDKRelay, NDKRelayStatus } from "./index";
import { NDK } from "../ndk/index";

jest.mock("ws");
jest.useFakeTimers();

describe("NDKRelayConnectivity", () => {
let ndkRelayConnectivity: NDKRelayConnectivity;
let ndkRelayMock: NDKRelay;
let relayConnectSpy: jest.SpyInstance;
let relayDisconnectSpy: jest.SpyInstance;

beforeEach(async () => {
ndkRelayMock = new NDKRelay("ws://localhost");
ndkRelayConnectivity = new NDKRelayConnectivity(ndkRelayMock);
// Mock the connect method on nostr tools relay
relayConnectSpy = jest.spyOn(ndkRelayConnectivity.relay, "connect").mockResolvedValue();
// Mock the close method on the nostr tools relay
relayDisconnectSpy = jest.spyOn(ndkRelayConnectivity.relay, "close").mockReturnValue();
await ndkRelayConnectivity.connect();
let ndk: NDK;
let relay: NDKRelay;
let connectivity: NDKRelayConnectivity;

beforeEach(() => {
ndk = new NDK();
relay = new NDKRelay("wss://test.relay");
connectivity = new NDKRelayConnectivity(relay, ndk);
});

afterEach(() => {
jest.restoreAllMocks();
jest.clearAllMocks();
});

describe("connecting", () => {
it("calls connect() on the nostr-tools AbstractRelay instance", () => {
expect(relayConnectSpy).toHaveBeenCalled();
describe("connect", () => {
it("should set status to CONNECTING when disconnected", async () => {
await connectivity.connect();
expect(connectivity.status).toBe(NDKRelayStatus.CONNECTING);
});

it("updates connected status properly", () => {
// Check that we updated our status
expect(ndkRelayConnectivity.status).toBe(NDKRelayStatus.CONNECTED);
// Check that we're available
expect(ndkRelayConnectivity.isAvailable()).toBe(true);
it("should set status to RECONNECTING when not disconnected", async () => {
connectivity["_status"] = NDKRelayStatus.CONNECTED;
await connectivity.connect();
expect(connectivity.status).toBe(NDKRelayStatus.RECONNECTING);
});

it("updates connectionStats on connect", () => {
expect(ndkRelayConnectivity.connectionStats.attempts).toBe(1);
expect(ndkRelayConnectivity.connectionStats.connectedAt).toBeDefined();
it("should create a new WebSocket connection", async () => {
const mockWebSocket = jest.fn();
global.WebSocket = mockWebSocket as any;

await connectivity.connect();
expect(mockWebSocket).toHaveBeenCalledWith("wss://test.relay/");
});
});

// TODO: Test auth

describe("disconnecting", () => {
describe("disconnect", () => {
beforeEach(() => {
ndkRelayConnectivity.disconnect();
connectivity["_status"] = NDKRelayStatus.CONNECTED;
});
it("should set status to DISCONNECTING", () => {
connectivity.disconnect();
expect(connectivity.status).toBe(NDKRelayStatus.DISCONNECTING);
});

it("should close the WebSocket connection", () => {
const mockClose = jest.fn();
connectivity["ws"] = { close: mockClose } as any;
connectivity.disconnect();
expect(mockClose).toHaveBeenCalled();
});

it("should handle disconnect error", () => {
const mockClose = jest.fn(() => {
throw new Error("Disconnect failed");
});
connectivity["ws"] = { close: mockClose } as any;
connectivity.disconnect();
expect(connectivity.status).toBe(NDKRelayStatus.DISCONNECTED);
});
});

it("disconnects from the relay", async () => {
expect(relayDisconnectSpy).toHaveBeenCalled();
describe("isAvailable", () => {
it("should return true when status is CONNECTED", () => {
connectivity["_status"] = NDKRelayStatus.CONNECTED;
expect(connectivity.isAvailable()).toBe(true);
});

it("updates connected status properly", () => {
expect(ndkRelayConnectivity.status).toBe(NDKRelayStatus.DISCONNECTING);
expect(ndkRelayConnectivity.isAvailable()).toBe(false);
it("should return false when status is not CONNECTED", () => {
connectivity["_status"] = NDKRelayStatus.DISCONNECTED;
expect(connectivity.isAvailable()).toBe(false);
});
});

// Test that onclose callback was properly called
it.skip("updates the connectionStats for disconnect", () => {
expect(ndkRelayConnectivity.connectionStats.connectedAt).toBe(undefined);
expect(ndkRelayConnectivity.connectionStats.durations.length).toBe(1);
describe("send", () => {
it("should send message when connected and WebSocket is open", async () => {
const mockSend = jest.fn();
connectivity["_status"] = NDKRelayStatus.CONNECTED;
connectivity["ws"] = { readyState: WebSocket.OPEN, send: mockSend } as any;
await connectivity.send("test message");
expect(mockSend).toHaveBeenCalledWith("test message");
});

// TODO: Can we test the emit on NDKRelay?
// TODO: Test reconnection logic (disconnect called from AbstractRelay)
it("should throw error when not connected", async () => {
connectivity["_status"] = NDKRelayStatus.DISCONNECTED;
await expect(connectivity.send("test message")).rejects.toThrow(
"Attempting to send on a closed relay connection"
);
});
});

describe("publish", () => {
it("should send EVENT message and return a promise", async () => {
const mockSend = jest.spyOn(connectivity, "send").mockResolvedValue(undefined);
const event = { id: "test-id", content: "test-content" };
const publishPromise = connectivity.publish(event as any);
expect(mockSend).toHaveBeenCalledWith(
'["EVENT",{"id":"test-id","content":"test-content"}]'
);
expect(publishPromise).toBeInstanceOf(Promise);
});
});

describe("count", () => {
it("should send COUNT message and return a promise", async () => {
const mockSend = jest.spyOn(connectivity, "send").mockResolvedValue(undefined);
const filters = [{ authors: ["test-author"] }];
const countPromise = connectivity.count(filters, {});
expect(mockSend).toHaveBeenCalledWith(
expect.stringMatching(/^\["COUNT","count:\d+",\{"authors":\["test-author"\]\}\]$/)
);
expect(countPromise).toBeInstanceOf(Promise);
});
});
});
Loading

0 comments on commit 8ad0be0

Please sign in to comment.