Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor!: make use of NIPs easier #32

Merged
merged 3 commits into from
Mar 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 16 additions & 44 deletions core/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,69 +4,41 @@ import type {
RelayToClientMessage,
SubscriptionId,
} from "./protocol.ts";
import {
NostrNode,
NostrNodeBase,
NostrNodeConfig,
NostrNodeModule,
} from "./nodes.ts";
import { Node, NodeConfig } from "./nodes.ts";

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

export type ClientConfig = NostrNodeConfig<
RelayToClientMessage,
ClientEventTypeRecord
>;
export type ClientConfig = NodeConfig;
export type ClientOptions = Partial<ClientConfig>;

export interface ClientEventTypeRecord {
message: ClientToRelayMessage;
}

/**
* A class that represents a remote Nostr client.
*/
export class Client extends NostrNodeBase<
export class Client extends Node<
RelayToClientMessage,
ClientEventTypeRecord
> implements NostrNode<RelayToClientMessage, ClientEventTypeRecord> {
/**
* The WebSocket connection to the client.
*/
> {
declare ws: WebSocket;
declare config: ClientConfig;

/**
* Writable interface for the subscriptions.
*/
/** Writable interface for the subscriptions. */
readonly subscriptions: Map<
SubscriptionId,
WritableStream<NostrEvent>
> = new Map();

constructor(ws: WebSocket, opts?: ClientOptions) {
super(ws, opts);
constructor(ws: WebSocket, options?: ClientOptions) {
super(ws, options);
this.config = {
...this.config,
...options,
};
this.ws.addEventListener("message", (ev: MessageEvent<string>) => {
const message = JSON.parse(ev.data) as ClientToRelayMessage;
// TODO: Validate the message.
this.dispatch("message", message);
});
}
}

// ------------------------------
// Events
// ------------------------------

export interface ClientEventTypeRecord {
"message": ClientToRelayMessage;
}

export type ClientEventType = keyof ClientEventTypeRecord;

// ------------------------------
// Modules
// ------------------------------

export type ClientModule = NostrNodeModule<
RelayToClientMessage,
ClientEventTypeRecord,
Client
>;
2 changes: 1 addition & 1 deletion core/clients_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { assert } from "@std/assert";
import { afterAll, beforeAll, describe, it } from "@std/testing/bdd";
import { MockWebSocket } from "../lib/testing.ts";
import { MockWebSocket } from "@lophus/lib/testing";
import { Client } from "./clients.ts";

describe("Client", () => {
Expand Down
16 changes: 10 additions & 6 deletions core/deno.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
{
"name": "@lophus/core",
"version": "0.0.14",
"exports": "./mod.ts",

"exports": {
".": "./mod.ts",
"./clients": "./clients.ts",
"./protocol": "./protocol.ts",
"./relays": "./relays.ts"
},
"imports": {
"@lophus/lib": "jsr:@lophus/[email protected]"
},
"publish": {
"exclude": [
"deno.json",
"*_test.ts"
]
"exclude": ["*_test.ts"]
}
}
2 changes: 1 addition & 1 deletion core/mod.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export * from "./clients.ts";
export * from "./protocol.ts";
export * from "./relays.ts";
export * from "./clients.ts";
173 changes: 57 additions & 116 deletions core/nodes.ts
Original file line number Diff line number Diff line change
@@ -1,95 +1,35 @@
import type { NostrMessage } from "./protocol.ts";
import { WebSocketLike } from "./websockets.ts";

export interface NostrNodeConfig<
W extends NostrMessage = NostrMessage,
R extends EventTypeRecord = EventTypeRecord,
> {
modules: NostrNodeModule<W, R>[];
import type { WebSocketLike } from "@lophus/lib/websockets";
import type { InterNodeMessage } from "./protocol.ts";

export interface NodeConfig {
nbuffer: number;
}

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

/**
* Common interface for relays and clients, which extends `EventTarget`.
*/
export interface NostrNode<
W extends NostrMessage = NostrMessage,
R extends EventTypeRecord = EventTypeRecord,
> {
readonly config: Readonly<NostrNodeConfig<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;

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

removeEventListener<T extends EventType<R>>(
type: T,
listener:
| NostrNodeEventListenerOrEventListenerObject<W, R, T>
| null,
options?: boolean | EventListenerOptions,
): void;

dispatchEvent<T extends EventType<R>>(event: NostrNodeEvent<R, T>): boolean;

/**
* A convenience method to dispatch a `NostrNodeEvent` with the given `type`
* and `data`.
*/
dispatch<T extends EventType<R>>(type: T, data: R[T]): void;

/**
* A convenience method to add an event listener for the given `type` that
* calls the given `listener` when the event is dispatched.
*/
on<T extends EventType<R>>(
type: T,
// deno-lint-ignore no-explicit-any
listener: (data: R[T]) => any,
): void;
}
export type NodeOptions = Partial<NodeConfig>;

/**
* Common base class for relays and clients.
*/
export class NostrNodeBase<
W extends NostrMessage = NostrMessage,
R extends EventTypeRecord = EventTypeRecord,
> extends EventTarget implements NostrNode<W, R> {
readonly writable: WritableStream<W>;
readonly config: Readonly<NostrNodeConfig<W, R>>;
export class Node<
M extends InterNodeMessage = InterNodeMessage,
R = AnyEventTypeRecord,
> extends EventTarget {
readonly writable: WritableStream<M>;
readonly config: Readonly<NodeConfig>;

constructor(
readonly ws: WebSocketLike,
opts: NostrNodeOptions = {},
options: NodeOptions = {},
) {
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 = { nbuffer: 10, ...options };
}

send(msg: W): void | Promise<void> {
send(msg: M): void | Promise<void> {
return this.ws.send(JSON.stringify(msg));
}

Expand All @@ -107,19 +47,31 @@ export class NostrNodeBase<
}
}

install(mod: NostrNodeModule<W, R>): void {
mod.install(this);
}
declare addEventListener: <T extends EventType<R>>(
type: T,
listener:
| NodeEventListenerOrEventListenerObject<M, R, T>
| null,
options?: AddEventListenerOptions,
) => void;

declare removeEventListener: <T extends EventType<R>>(
type: T,
listener:
| NodeEventListenerOrEventListenerObject<M, R, T>
| null,
options?: boolean | EventListenerOptions,
) => void;

declare addEventListener: NostrNode<W, R>["addEventListener"];
declare removeEventListener: NostrNode<W, R>["removeEventListener"];
declare dispatchEvent: NostrNode<W, R>["dispatchEvent"];
declare dispatchEvent: <T extends EventType<R>>(
event: NodeEvent<R, T>,
) => boolean;

dispatch<T extends EventType<R>>(
type: T,
data: R[T],
) {
this.dispatchEvent(new NostrNodeEvent<R, T>(type, data));
this.dispatchEvent(new NodeEvent<R, T>(type, data));
}

on<T extends EventType<R>>(
Expand All @@ -131,56 +83,45 @@ export class NostrNodeBase<
}
}

// ------------------------------
// Extensions
// ------------------------------

export interface NostrNodeModule<
W extends NostrMessage = NostrMessage,
R extends EventTypeRecord = EventTypeRecord,
N extends NostrNode<W, R> = NostrNode<W, R>,
> {
// deno-lint-ignore no-explicit-any
install(node: N): any;
}

// ------------------------------
// Events
// ------------------------------

// deno-lint-ignore no-empty-interface
export interface EventTypeRecord {}
// deno-lint-ignore no-explicit-any
type AnyEventTypeRecord = any;

type EventType<R extends EventTypeRecord> = keyof R & string;
type EventType<R = AnyEventTypeRecord> = keyof R & string;

export class NostrNodeEvent<
R extends EventTypeRecord,
T extends EventType<R>,
export class NodeEvent<
R = AnyEventTypeRecord,
T extends EventType<R> = EventType<R>,
> extends MessageEvent<R[T]> {
declare type: T;
constructor(type: T, data: R[T]) {
super(type, { data });
}
}

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

type NostrNodeEventListener<
W extends NostrMessage,
R extends EventTypeRecord,
T extends EventType<R>,
type NodeEventListenerOrEventListenerObject<
M extends InterNodeMessage,
R = AnyEventTypeRecord,
T extends EventType<R> = EventType<R>,
> =
| NodeEventListener<M, R, T>
| NodeEventListenerObject<M, R, T>;

type NodeEventListener<
W extends InterNodeMessage,
R = AnyEventTypeRecord,
T extends EventType<R> = EventType<R>,
> // deno-lint-ignore no-explicit-any
= (this: NostrNode<W, R>, ev: MessageEvent<R[T]>) => any;
= (this: Node<W, R>, ev: MessageEvent<R[T]>) => any;

type NostrNodeEventListenerObject<
W extends NostrMessage,
R extends EventTypeRecord,
T extends EventType<R>,
type NodeEventListenerObject<
W extends InterNodeMessage,
R = AnyEventTypeRecord,
T extends EventType<R> = EventType<R>,
> = {
// deno-lint-ignore no-explicit-any
handleEvent(this: NostrNode<W, R>, ev: MessageEvent<R[T]>): any;
handleEvent(this: Node<W, R>, ev: MessageEvent<R[T]>): any;
};
Loading