Skip to content

Commit

Permalink
WIP: refactor: nip module
Browse files Browse the repository at this point in the history
  • Loading branch information
hasundue committed Mar 15, 2024
1 parent c8ff711 commit 0bac2d4
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 147 deletions.
25 changes: 15 additions & 10 deletions core/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,26 @@ import {
NostrNodeConfig,
NostrNodeModule,
} from "./nodes.ts";
import { NIP } from "@lophus/core/protocol";

// ----------------------
// Interfaces
// ----------------------

export type ClientConfig = NostrNodeConfig<
export type ClientConfig<N extends NIP = never> = NostrNodeConfig<
RelayToClientMessage,
ClientEventTypeRecord
>;
export type ClientOptions = Partial<ClientConfig>;
export type ClientOptions<N extends NIP = never> = Partial<ClientConfig<N>>;

/**
* A class that represents a remote Nostr client.
*/
export class Client extends NostrNodeBase<
export class Client<N extends NIP = never> extends NostrNodeBase<
RelayToClientMessage,
ClientEventTypeRecord
> implements NostrNode<RelayToClientMessage, ClientEventTypeRecord> {
ClientEventTypeRecord,
N
> implements NostrNode<RelayToClientMessage, ClientEventTypeRecord, N> {
/**
* The WebSocket connection to the client.
*/
Expand Down Expand Up @@ -65,8 +67,11 @@ export type ClientEventType = keyof ClientEventTypeRecord;
// Modules
// ------------------------------

export type ClientModule = NostrNodeModule<
RelayToClientMessage,
ClientEventTypeRecord,
Client
>;
export type ClientModule<P extends NIP = never, D extends NIP = never> =
NostrNodeModule<
RelayToClientMessage,
ClientEventTypeRecord,
D,
P,
Client<P | D>
>;
3 changes: 2 additions & 1 deletion core/mod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./clients.ts";
export * from "./nodes.ts";
export * from "./protocol.ts";
export * from "./relays.ts";
export * from "./clients.ts";
52 changes: 31 additions & 21 deletions core/nodes.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,51 @@
import type { NostrMessage } from "./protocol.ts";
import type { NIP, NostrMessage } from "./protocol.ts";
import { WebSocketLike } from "./websockets.ts";

export interface NostrNodeConfig<
N extends NIP = never,
W extends NostrMessage = NostrMessage,
R extends EventTypeRecord = EventTypeRecord,
> {
modules: NostrNodeModule<W, R>[];
modules: NostrNodeModule<N, W, R, N>[];
nbuffer: number;
}

export type NostrNodeOptions<
N extends NIP = never,
W extends NostrMessage = NostrMessage,
R extends EventTypeRecord = EventTypeRecord,
> = Partial<NostrNodeConfig<W, R>>;
> = Partial<NostrNodeConfig<N, W, R>>;

/**
* Common interface for relays and clients, which extends `EventTarget`.
*/
export interface NostrNode<
N extends NIP = never,
W extends NostrMessage = NostrMessage,
R extends EventTypeRecord = EventTypeRecord,
> {
readonly config: Readonly<NostrNodeConfig<W, R>>;
readonly config: Readonly<NostrNodeConfig<N, W, R>>;
readonly ws: WebSocketLike;
readonly writable: WritableStream<W>;

status: WebSocketLike["readyState"];
send(msg: W): void | Promise<void>;
close(): Promise<void>;

install(mod: NostrNodeModule<W, R>): void;
install?(mod: NostrNodeModule<N, W, R>): void;

addEventListener<T extends EventType<R>>(
type: T,
listener:
| NostrNodeEventListenerOrEventListenerObject<W, R, T>
| NostrNodeEventListenerOrEventListenerObject<N, W, R, T>
| null,
options?: AddEventListenerOptions,
): void;

removeEventListener<T extends EventType<R>>(
type: T,
listener:
| NostrNodeEventListenerOrEventListenerObject<W, R, T>
| NostrNodeEventListenerOrEventListenerObject<N, W, R, T>
| null,
options?: boolean | EventListenerOptions,
): void;
Expand Down Expand Up @@ -70,23 +73,24 @@ export interface NostrNode<
* Common base class for relays and clients.
*/
export class NostrNodeBase<
N extends NIP = never,
W extends NostrMessage = NostrMessage,
R extends EventTypeRecord = EventTypeRecord,
> extends EventTarget implements NostrNode<W, R> {
> extends EventTarget implements NostrNode<N, W, R> {
readonly writable: WritableStream<W>;
readonly config: Readonly<NostrNodeConfig<W, R>>;
readonly config: Readonly<NostrNodeConfig<N, W, R>>;

constructor(
readonly ws: WebSocketLike,
opts: NostrNodeOptions = {},
opts: NostrNodeOptions<N, W, R> = {},
) {
super();
this.writable = new WritableStream({
write: (msg) => this.ws.send(JSON.stringify(msg)),
close: () => this.ws.close(),
});
this.config = { modules: [], nbuffer: 10, ...opts };
this.config.modules.forEach((m) => this.install(m));
this.config.modules.forEach((m) => this.install?.(m));
}

send(msg: W): void | Promise<void> {
Expand All @@ -107,13 +111,13 @@ export class NostrNodeBase<
}
}

install(mod: NostrNodeModule<W, R>): void {
install(mod: NostrNodeModule<N, W, R, N>): void {
mod.install(this);
}

declare addEventListener: NostrNode<W, R>["addEventListener"];
declare removeEventListener: NostrNode<W, R>["removeEventListener"];
declare dispatchEvent: NostrNode<W, R>["dispatchEvent"];
declare addEventListener: NostrNode<N, W, R>["addEventListener"];
declare removeEventListener: NostrNode<N, W, R>["removeEventListener"];
declare dispatchEvent: NostrNode<N, W, R>["dispatchEvent"];

dispatch<T extends EventType<R>>(
type: T,
Expand All @@ -136,12 +140,13 @@ export class NostrNodeBase<
// ------------------------------

export interface NostrNodeModule<
P extends NIP = never, // NIPs that this module provides
W extends NostrMessage = NostrMessage,
R extends EventTypeRecord = EventTypeRecord,
N extends NostrNode<W, R> = NostrNode<W, R>,
D extends NIP = never, // NIPs that this module depends on
N extends NostrNode<D | P, W, R> = NostrNode<D | P, W, R>,
> {
// deno-lint-ignore no-explicit-any
install(node: N): any;
install(node: N): void | Promise<void>;
}

// ------------------------------
Expand All @@ -164,23 +169,28 @@ export class NostrNodeEvent<
}

type NostrNodeEventListenerOrEventListenerObject<
N extends NIP,
W extends NostrMessage,
R extends EventTypeRecord,
T extends EventType<R>,
> = NostrNodeEventListener<W, R, T> | NostrNodeEventListenerObject<W, R, T>;
> =
| NostrNodeEventListener<N, W, R, T>
| NostrNodeEventListenerObject<N, W, R, T>;

type NostrNodeEventListener<
N extends NIP,
W extends NostrMessage,
R extends EventTypeRecord,
T extends EventType<R>,
> // deno-lint-ignore no-explicit-any
= (this: NostrNode<W, R>, ev: MessageEvent<R[T]>) => any;
= (this: NostrNode<N, W, R>, ev: MessageEvent<R[T]>) => any;

type NostrNodeEventListenerObject<
N extends NIP,
W extends NostrMessage,
R extends EventTypeRecord,
T extends EventType<R>,
> = {
// deno-lint-ignore no-explicit-any
handleEvent(this: NostrNode<W, R>, ev: MessageEvent<R[T]>): any;
handleEvent(this: NostrNode<N, W, R>, ev: MessageEvent<R[T]>): any;
};
52 changes: 31 additions & 21 deletions core/nodes_test.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
import { assertEquals } from "@std/assert";
import { afterAll, beforeAll, describe, it } from "@std/testing/bdd";
import { assert, assertEquals } from "@std/assert";
import { assertType, Has } from "@std/testing/types";
import { describe, it } from "@std/testing/bdd";
import { MockWebSocket } from "@lophus/lib/testing";
import { NostrNode, NostrNodeBase } from "./nodes.ts";
import { NostrNode, NostrNodeBase, NostrNodeModule } from "./nodes.ts";

describe("NostrNodeBase", () => {
let node: NostrNode;
let writer: WritableStreamDefaultWriter;
describe("constructor", () => {
it("should create a NostrNodeBase instance", () => {
const node = new NostrNodeBase(new MockWebSocket());
assert(node instanceof NostrNodeBase);
});

beforeAll(() => {
node = new NostrNodeBase(new MockWebSocket());
it("should infer the type of `modules` option", () => {
const node = new NostrNodeBase(new MockWebSocket(), {
modules: [{} as NostrNodeModule<1>, {} as NostrNodeModule<2>] as const,
nbuffer: 64,
});
assertType<Has<typeof node, NostrNode<1 | 2>>>(true);
});
});

afterAll(async () => {
await node.close().catch((err) => {
if (err.message !== "Writable stream is closed or errored.") {
throw err;
}
describe("writable", () => {
const node = new NostrNodeBase(new MockWebSocket());
let writer: WritableStreamDefaultWriter;

it("should open the WebSocket connection", async () => {
writer = node.writable.getWriter();
await writer.write(["NOTICE", "test"]);
writer.releaseLock();
assertEquals(node.status, WebSocket.OPEN);
});
});

it("should be connected to the WebSocket after a message is sent", async () => {
writer = node.writable.getWriter();
await writer.write(["NOTICE", "test"]);
writer.releaseLock();
assertEquals(node.status, WebSocket.OPEN);
});
describe("close", () => {
const node = new NostrNodeBase(new MockWebSocket());

it("should close the WebSocket when the node is closed", async () => {
await node.close();
assertEquals(node.status, WebSocket.CLOSED);
it("should close the WebSocket connection", async () => {
await node.close();
assertEquals(node.status, WebSocket.CLOSED);
});
});
});
36 changes: 21 additions & 15 deletions core/relays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
ClientToRelayMessage,
EventId,
EventKind,
NIP,
NostrEvent,
RelayToClientMessage,
RelayToClientMessageType,
Expand All @@ -17,24 +18,25 @@ import { NostrNodeBase, NostrNodeConfig, NostrNodeModule } from "./nodes.ts";
// Errors
// ----------------------

export class EventRejected extends Error {}
export class ConnectionClosed extends Error {}
export class EventRejected extends Error {}
export class SubscriptionClosed extends Error {}

// ----------------------
// Interfaces
// ----------------------

export interface RelayConfig
extends NostrNodeConfig<ClientToRelayMessage, RelayEventTypeRecord> {
export interface RelayConfig<N extends NIP = never>
extends NostrNodeConfig<N, ClientToRelayMessage, RelayEventTypeRecord> {
url: RelayUrl;
name: string;
read: boolean;
write: boolean;
}

export type RelayOptions = Partial<RelayConfig>;
export type RelayOptions<N extends NIP = never> = Partial<RelayConfig<N>>;

export interface RelayInit extends RelayOptions {
export interface RelayInit<N extends NIP = never> extends RelayOptions<N> {
url: RelayUrl;
}

Expand All @@ -47,19 +49,20 @@ export interface SubscriptionOptions {
/**
* A class that represents a remote Nostr Relay.
*/
export class Relay extends NostrNodeBase<
export class Relay<N extends NIP = never> extends NostrNodeBase<
N,
ClientToRelayMessage,
RelayEventTypeRecord
> {
declare ws: LazyWebSocket;
declare config: RelayConfig;
declare config: RelayConfig<N>;

constructor(
init: RelayUrl | RelayInit,
options?: RelayOptions,
init: RelayUrl | RelayInit<N>,
options?: RelayOptions<N>,
) {
const url = typeof init === "string" ? init : init.url;
const config = { nbuffer: 64, modules: [], ...options };
const config = { nbuffer: 64, nips: [], modules: [], ...options };
super(new LazyWebSocket(url), config);
this.config = {
url,
Expand Down Expand Up @@ -189,8 +192,11 @@ export type RelayEventType = keyof RelayEventTypeRecord;
// Modules
// ----------------------

export type RelayModule = NostrNodeModule<
ClientToRelayMessage,
RelayEventTypeRecord,
Relay
>;
export type RelayModule<P extends NIP = never, D extends NIP = never> =
NostrNodeModule<
P,
ClientToRelayMessage,
RelayEventTypeRecord,
D,
Relay<P | D>
>;
Loading

0 comments on commit 0bac2d4

Please sign in to comment.