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 b5ea615
Show file tree
Hide file tree
Showing 16 changed files with 286 additions and 291 deletions.
59 changes: 20 additions & 39 deletions core/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,38 +4,29 @@ import type {
RelayToClientMessage,
SubscriptionId,
} from "./protocol.ts";
import {
NostrNode,
NostrNodeBase,
NostrNodeConfig,
NostrNodeModule,
} from "./nodes.ts";
import { ClientEventTypeRecord, NostrNode, NostrNodeConfig } from "./nodes.ts";
import { NIP } from "@lophus/core/protocol";

// ----------------------
//-------------
// Interfaces
// ----------------------
//-------------
export interface ClientConfig<N extends NIP = never> extends NostrNodeConfig<N> {
modules: ClientModule<N, NIP>[];
};

export type ClientConfig = 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 NostrNode<
N,
RelayToClientMessage,
ClientEventTypeRecord
> implements NostrNode<RelayToClientMessage, ClientEventTypeRecord> {
/**
* The WebSocket connection to the client.
*/
> {
declare ws: WebSocket;

/**
* Writable interface for the subscriptions.
*/
/** Writable interface for the subscriptions. */
readonly subscriptions: Map<
SubscriptionId,
WritableStream<NostrEvent>
Expand All @@ -51,22 +42,12 @@ export class Client extends NostrNodeBase<
}
}

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

export interface ClientEventTypeRecord {
"message": ClientToRelayMessage;
}

export type ClientEventType = keyof ClientEventTypeRecord;

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

export type ClientModule = NostrNodeModule<
RelayToClientMessage,
ClientEventTypeRecord,
Client
>;
//----------
export interface ClientModule<
P extends NIP = never,
D extends NIP = never,
> {
(client: Client<P | D>): void;
}
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";
184 changes: 93 additions & 91 deletions core/nodes.ts
Original file line number Diff line number Diff line change
@@ -1,95 +1,52 @@
import type { NostrMessage } from "./protocol.ts";
import { WebSocketLike } from "./websockets.ts";
import type { PromiseCallbackRecord } from "@lophus/lib/types";
import type {
ClientToRelayMessage,
EventId,
NIP,
NostrEvent,
NostrMessage,
RelayToClientMessage,
RelayToClientMessageType,
SubscriptionFilter,
SubscriptionId,
} from "./protocol.ts";
import type { WebSocketLike } from "./websockets.ts";

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

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;
}
N extends NIP = never,
> = Partial<NostrNodeConfig<N>>;

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

constructor(
readonly ws: WebSocketLike,
opts: NostrNodeOptions = {},
opts: NostrNodeOptions<N> = {},
) {
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, nips: [], ...opts };
}

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

Expand All @@ -107,13 +64,25 @@ export class NostrNodeBase<
}
}

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

declare addEventListener: NostrNode<W, R>["addEventListener"];
declare removeEventListener: NostrNode<W, R>["removeEventListener"];
declare dispatchEvent: NostrNode<W, R>["dispatchEvent"];
declare removeEventListener: <T extends EventType<R>>(
type: T,
listener:
| NostrNodeEventListenerOrEventListenerObject<N, M, R, T>
| null,
options?: boolean | EventListenerOptions,
) => void;

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

dispatch<T extends EventType<R>>(
type: T,
Expand All @@ -131,25 +100,53 @@ export class NostrNodeBase<
}
}

// ------------------------------
// Extensions
// ------------------------------
//-------------------------
// Messages and contexts
//-------------------------

type SubscriptionMessage = {
[T in RelayToClientMessageType]: RelayToClientMessage<T>[1] extends
SubscriptionId ? RelayToClientMessage<T>
: never;
}[RelayToClientMessageType];

type PublicationMessage = {
[T in RelayToClientMessageType]: RelayToClientMessage<T>[1] extends EventId
? RelayToClientMessage<T>
: never;
}[RelayToClientMessageType];

export interface SubscriptionContext {
id: SubscriptionId;
filters: SubscriptionFilter[];
realtime: boolean;
}

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;
export interface PublicationContext extends PromiseCallbackRecord<void> {
event: NostrEvent;
}

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

// deno-lint-ignore no-empty-interface
export interface EventTypeRecord {}
type EventTypeRecord = RelayEventTypeRecord | ClientEventTypeRecord;

export interface RelayEventTypeRecord {
message: RelayToClientMessage;
subscribe: SubscriptionContext & {
controller: ReadableStreamDefaultController<NostrEvent>;
};
resubscribe: SubscriptionContext;
unsubscribe: SubscriptionContext & { reason: unknown };
publish: PublicationContext;
[id: SubscriptionId]: SubscriptionMessage;
[id: EventId]: PublicationMessage;
}

export interface ClientEventTypeRecord {
message: ClientToRelayMessage;
}

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

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

type NostrNodeEventListenerOrEventListenerObject<
W extends NostrMessage,
N extends NIP,
M extends NostrMessage,
R extends EventTypeRecord,
T extends EventType<R>,
> = NostrNodeEventListener<W, R, T> | NostrNodeEventListenerObject<W, R, T>;
> =
| NostrNodeEventListener<N, M, R, T>
| NostrNodeEventListenerObject<N, M, 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;
};
Loading

0 comments on commit b5ea615

Please sign in to comment.