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: simplify modular architecture #27

Merged
merged 16 commits into from
Feb 15, 2024
99 changes: 35 additions & 64 deletions core/clients.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type {
ClientToRelayMessage,
ClientToRelayMessageType,
NostrEvent,
RelayToClientMessage,
SubscriptionFilter,
SubscriptionId,
} from "./protocol.d.ts";
import {
Expand All @@ -12,21 +10,35 @@ import {
NostrNodeEvent,
NostrNodeModule,
} from "./nodes.ts";
import { NIPs } from "./nips.ts";
import { importNips } from "./nips.ts";

// ----------------------
// NIPs
// ----------------------

const nips = await NIPs.import<ClientModule>(import.meta.url, "../nips");
const NIPs = await importNips<
RelayToClientMessage,
ClientEventTypeRecord,
Client
>(import.meta.url, "../nips");

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

export type ClientConfig = NostrNodeConfig<
RelayToClientMessage,
ClientEventTypeRecord
>;

export type ClientOptions = Partial<ClientConfig>;

/**
* A class that represents a remote Nostr client.
*/
export class Client extends NostrNode<
RelayToClientMessage,
EventDataTypeRecord,
ClientFunctionParameterTypeRecord
ClientEventTypeRecord
> {
declare ws: WebSocket;

Expand All @@ -41,77 +53,36 @@ export class Client extends NostrNode<
constructor(ws: WebSocket, opts?: ClientOptions) {
super(ws, {
...opts,
modules: nips.concat(opts?.modules ?? []),
modules: NIPs.concat(opts?.modules ?? []),
});
this.ws.addEventListener("message", (ev: MessageEvent<string>) => {
// TODO: Validate the type of the message.
const message = JSON.parse(ev.data) as ClientToRelayMessage;
this.callFunction("handleClientToRelayMessage", {
message,
client: this,
});
// TODO: Validate the message.
this.dispatchEvent(new ClientEvent("message", message));
});
}
}

type ClientConfig = NostrNodeConfig<ClientFunctionParameterTypeRecord>;
export type ClientOptions = Partial<ClientConfig>;

// ------------------------------
// Functions
// Events
// ------------------------------

export type ClientModule = NostrNodeModule<ClientFunctionParameterTypeRecord>;

type ClientFunctionParameterTypeRecord = {
[K in keyof _FunctionParameterTypeRecord]:
& _FunctionParameterTypeRecord[K]
& ClientFunctionContext;
};

type _FunctionParameterTypeRecord = {
"handleClientToRelayMessage": {
message: ClientToRelayMessage;
};
"handleSubscriptionMessage": {
message: SubscriptionMessage;
controller: ReadableStreamDefaultController<NostrEvent>;
} & SubscriptionContext;
"acceptEvent": {
event: NostrEvent;
};
};

interface ClientFunctionContext {
client: Client;
export interface ClientEventTypeRecord {
"message": ClientToRelayMessage;
}

interface SubscriptionContext {
id: SubscriptionId;
filters: SubscriptionFilter[];
}
export type ClientEventType = keyof ClientEventTypeRecord;

export class ClientEvent<
T extends ClientEventType = ClientEventType,
> extends NostrNodeEvent<ClientEventTypeRecord, T> {}

// ------------------------------
// Events
// Modules
// ------------------------------

type EventDataTypeRecord = {
[T in SubscriptionId]: SubscriptionMessage;
};

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

export class ClientSubscriptionEvent extends NostrNodeEvent<
EventDataTypeRecord,
SubscriptionId
> {
constructor(
type: SubscriptionId,
init: MessageEventInit<SubscriptionMessage>,
) {
super(type, init);
}
}
export type ClientModule = NostrNodeModule<
RelayToClientMessage,
ClientEventTypeRecord,
Client
>;
59 changes: 30 additions & 29 deletions core/nips.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,34 @@
import type { NIP } from "./protocol.d.ts";
import { NostrNodeModule } from "./nodes.ts";
import type { NIP, NostrMessage } from "./protocol.d.ts";
import { EventTypeRecord, NostrNode, NostrNodeModule } from "./nodes.ts";

export const NIPs = {
/**
* Import a Nostr module from a URL.
*
* @param meta - The path to the module to which the NIPs are attached (mostly import.meta.url).
* @param root - The path to the root of NIP module to import.
*/
// deno-lint-ignore no-explicit-any
import<M extends NostrNodeModule<any>>(
meta: string,
root: string,
) {
const url = new URL(meta);
const base = url.pathname.split("/").slice(-1)[0];
return Promise.all(
url.searchParams.get("nips")?.split(",").map(Number).map(
(nip) =>
import(
new URL(
`${root}/${nipToString(nip)}/${base}`,
import.meta.url,
).href
) as Promise<M>,
) ?? [],
);
},
};
/**
* Import a NostrNode module from a URL.
*
* @param meta - The path to the module to which the NIPs are attached (mostly import.meta.url).
* @param root - The path to the root of NIP module to import.
*/
export function importNips<
W extends NostrMessage = NostrMessage,
R extends EventTypeRecord = EventTypeRecord,
N extends NostrNode<W, R> = NostrNode<W, R>,
>(
meta: string,
root: string,
) {
const url = new URL(meta);
const base = url.pathname.split("/").slice(-1)[0];
return Promise.all(
url.searchParams.get("nips")?.split(",").map(Number).map(
(nip) =>
import(
new URL(
`${root}/${nipToString(nip)}/${base}`,
import.meta.url,
).href
) as Promise<NostrNodeModule<W, R, N>>,
) ?? [],
);
}

/**
* Convert a NIP to a string. If the NIP is less than 10, a leading zero is
Expand Down
Loading