From ba1857df0749a480b32c64134d635a0f00bf7674 Mon Sep 17 00:00:00 2001 From: hasundue Date: Fri, 15 Mar 2024 13:14:00 +0900 Subject: [PATCH] WIP: refactor: nip module --- core/clients.ts | 71 ++++++-------- core/mod.ts | 2 +- core/nodes.ts | 184 ++++++++++++++++++----------------- core/nodes_test.ts | 53 ++++++---- core/relays.ts | 165 ++++++++++++------------------- core/relays_test.ts | 41 ++++---- deno.json | 6 +- lib/types.ts | 4 +- nips/01/clients.ts | 4 +- nips/01/clients_test.ts | 6 +- nips/01/relays.ts | 20 ++-- nips/01/relays_test.ts | 17 ++-- nips/42/relays.ts | 5 +- nips/_common.ts | 9 ++ nips/clients.ts | 15 +++ nips/deno.json | 2 +- nips/{mod.ts => protocol.ts} | 0 nips/relays.ts | 15 +++ 18 files changed, 316 insertions(+), 303 deletions(-) create mode 100644 nips/_common.ts create mode 100644 nips/clients.ts rename nips/{mod.ts => protocol.ts} (100%) create mode 100644 nips/relays.ts diff --git a/core/clients.ts b/core/clients.ts index e911611..544c5fc 100644 --- a/core/clients.ts +++ b/core/clients.ts @@ -4,69 +4,58 @@ 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 + extends NostrNodeConfig { + modules: ClientModule[]; +} -export type ClientConfig = NostrNodeConfig< - RelayToClientMessage, - ClientEventTypeRecord ->; -export type ClientOptions = Partial; +export type ClientOptions = Partial>; /** * A class that represents a remote Nostr client. */ -export class Client extends NostrNodeBase< +export class Client extends NostrNode< + N, RelayToClientMessage, ClientEventTypeRecord -> implements NostrNode { - /** - * 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 > = new Map(); - constructor(ws: WebSocket, opts?: ClientOptions) { - super(ws, opts); + constructor(ws: WebSocket, options?: ClientOptions) { + super(ws, options); + this.config = { + ...this.config, + modules: [], + ...options, + }; this.ws.addEventListener("message", (ev: MessageEvent) => { const message = JSON.parse(ev.data) as ClientToRelayMessage; // TODO: Validate the message. this.dispatch("message", message); }); + this.config.modules.forEach((mod) => mod(this)); } } -// ------------------------------ -// 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

): void | Promise; +} diff --git a/core/mod.ts b/core/mod.ts index 584b190..c73f17f 100644 --- a/core/mod.ts +++ b/core/mod.ts @@ -1,3 +1,3 @@ +export * from "./clients.ts"; export * from "./protocol.ts"; export * from "./relays.ts"; -export * from "./clients.ts"; diff --git a/core/nodes.ts b/core/nodes.ts index 48f4655..732550c 100644 --- a/core/nodes.ts +++ b/core/nodes.ts @@ -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[]; nbuffer: number; + nips: N[]; } export type NostrNodeOptions< - W extends NostrMessage = NostrMessage, - R extends EventTypeRecord = EventTypeRecord, -> = Partial>; - -/** - * Common interface for relays and clients, which extends `EventTarget`. - */ -export interface NostrNode< - W extends NostrMessage = NostrMessage, - R extends EventTypeRecord = EventTypeRecord, -> { - readonly config: Readonly>; - readonly ws: WebSocketLike; - readonly writable: WritableStream; - - status: WebSocketLike["readyState"]; - send(msg: W): void | Promise; - close(): Promise; - - install(mod: NostrNodeModule): void; - - addEventListener>( - type: T, - listener: - | NostrNodeEventListenerOrEventListenerObject - | null, - options?: AddEventListenerOptions, - ): void; - - removeEventListener>( - type: T, - listener: - | NostrNodeEventListenerOrEventListenerObject - | null, - options?: boolean | EventListenerOptions, - ): void; - - dispatchEvent>(event: NostrNodeEvent): boolean; - - /** - * A convenience method to dispatch a `NostrNodeEvent` with the given `type` - * and `data`. - */ - dispatch>(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>( - type: T, - // deno-lint-ignore no-explicit-any - listener: (data: R[T]) => any, - ): void; -} + N extends NIP = never, +> = Partial>; /** * 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 { - readonly writable: WritableStream; - readonly config: Readonly>; +> extends EventTarget { + readonly writable: WritableStream; + readonly config: Readonly>; constructor( readonly ws: WebSocketLike, - opts: NostrNodeOptions = {}, + opts: NostrNodeOptions = {}, ) { 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 { + send(msg: M): void | Promise { return this.ws.send(JSON.stringify(msg)); } @@ -107,13 +64,25 @@ export class NostrNodeBase< } } - install(mod: NostrNodeModule): void { - mod.install(this); - } + declare addEventListener: >( + type: T, + listener: + | NostrNodeEventListenerOrEventListenerObject + | null, + options?: AddEventListenerOptions, + ) => void; - declare addEventListener: NostrNode["addEventListener"]; - declare removeEventListener: NostrNode["removeEventListener"]; - declare dispatchEvent: NostrNode["dispatchEvent"]; + declare removeEventListener: >( + type: T, + listener: + | NostrNodeEventListenerOrEventListenerObject + | null, + options?: boolean | EventListenerOptions, + ) => void; + + declare dispatchEvent: >( + event: NostrNodeEvent, + ) => boolean; dispatch>( type: T, @@ -131,25 +100,53 @@ export class NostrNodeBase< } } -// ------------------------------ -// Extensions -// ------------------------------ +//------------------------- +// Messages and contexts +//------------------------- + +type SubscriptionMessage = { + [T in RelayToClientMessageType]: RelayToClientMessage[1] extends + SubscriptionId ? RelayToClientMessage + : never; +}[RelayToClientMessageType]; + +type PublicationMessage = { + [T in RelayToClientMessageType]: RelayToClientMessage[1] extends EventId + ? RelayToClientMessage + : 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 = NostrNode, -> { - // deno-lint-ignore no-explicit-any - install(node: N): any; +export interface PublicationContext extends PromiseCallbackRecord { + 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; + }; + resubscribe: SubscriptionContext; + unsubscribe: SubscriptionContext & { reason: unknown }; + publish: PublicationContext; + [id: SubscriptionId]: SubscriptionMessage; + [id: EventId]: PublicationMessage; +} + +export interface ClientEventTypeRecord { + message: ClientToRelayMessage; +} type EventType = keyof R & string; @@ -164,23 +161,28 @@ export class NostrNodeEvent< } type NostrNodeEventListenerOrEventListenerObject< - W extends NostrMessage, + N extends NIP, + M extends NostrMessage, R extends EventTypeRecord, T extends EventType, -> = NostrNodeEventListener | NostrNodeEventListenerObject; +> = + | NostrNodeEventListener + | NostrNodeEventListenerObject; type NostrNodeEventListener< + N extends NIP, W extends NostrMessage, R extends EventTypeRecord, T extends EventType, > // deno-lint-ignore no-explicit-any - = (this: NostrNode, ev: MessageEvent) => any; + = (this: NostrNode, ev: MessageEvent) => any; type NostrNodeEventListenerObject< + N extends NIP, W extends NostrMessage, R extends EventTypeRecord, T extends EventType, > = { // deno-lint-ignore no-explicit-any - handleEvent(this: NostrNode, ev: MessageEvent): any; + handleEvent(this: NostrNode, ev: MessageEvent): any; }; diff --git a/core/nodes_test.ts b/core/nodes_test.ts index 57b4b4e..86aa0d5 100644 --- a/core/nodes_test.ts +++ b/core/nodes_test.ts @@ -1,33 +1,44 @@ import { assertEquals } from "@std/assert"; -import { afterAll, beforeAll, describe, it } from "@std/testing/bdd"; +import { describe, it } from "@std/testing/bdd"; +import { assertType, Has } from "@std/testing/types"; import { MockWebSocket } from "@lophus/lib/testing"; -import { NostrNode, NostrNodeBase } from "./nodes.ts"; +import "@lophus/nips"; +import { NIP } from "./protocol.ts"; +import { NostrNode } from "./nodes.ts"; -describe("NostrNodeBase", () => { - let node: NostrNode; - let writer: WritableStreamDefaultWriter; +type HasNIP = T extends NostrNode ? Has : false; - beforeAll(() => { - node = new NostrNodeBase(new MockWebSocket()); - }); +describe("NostrNode", () => { + describe("constructor", () => { + it("should not have any NIPs configured by default", () => { + const node = new NostrNode(new MockWebSocket()); + assertType>(false); + }); - afterAll(async () => { - await node.close().catch((err) => { - if (err.message !== "Writable stream is closed or errored.") { - throw err; - } + it("should inherit the type of `nips`", () => { + const node = new NostrNode(new MockWebSocket(), { nips: [1] }); + assertType>(true); }); }); - 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("writable", () => { + const node = new NostrNode(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 close the WebSocket when the node is closed", async () => { - await node.close(); - assertEquals(node.status, WebSocket.CLOSED); + describe("close", () => { + const node = new NostrNode(new MockWebSocket()); + + it("should close the WebSocket connection", async () => { + await node.close(); + assertEquals(node.status, WebSocket.CLOSED); + }); }); }); diff --git a/core/relays.ts b/core/relays.ts index 66a92b9..9f4a67c 100644 --- a/core/relays.ts +++ b/core/relays.ts @@ -1,72 +1,73 @@ import type { Stringified } from "@lophus/lib/types"; import type { ClientToRelayMessage, - EventId, EventKind, + NIP, NostrEvent, RelayToClientMessage, - RelayToClientMessageType, RelayUrl, SubscriptionFilter, SubscriptionId, } from "./protocol.ts"; import { LazyWebSocket } from "./websockets.ts"; -import { NostrNodeBase, NostrNodeConfig, NostrNodeModule } from "./nodes.ts"; +import { NostrNode, NostrNodeConfig, RelayEventTypeRecord } 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 { - url: RelayUrl; +//------------- +export interface RelayConfig extends NostrNodeConfig { name: string; + modules: RelayModule[]; read: boolean; + url: RelayUrl; write: boolean; } -export type RelayOptions = Partial; +export type RelayOptions = Partial>; -export interface RelayInit extends RelayOptions { +export interface RelayInit extends RelayOptions { url: RelayUrl; } export interface SubscriptionOptions { id?: string; - realtime?: boolean; nbuffer?: number; + realtime?: boolean; } /** * A class that represents a remote Nostr Relay. */ -export class Relay extends NostrNodeBase< +export class Relay extends NostrNode< + N, ClientToRelayMessage, RelayEventTypeRecord > { + #ready: Promise; declare ws: LazyWebSocket; - declare config: RelayConfig; + declare config: RelayConfig; constructor( - init: RelayUrl | RelayInit, - options?: RelayOptions, + init: RelayUrl | RelayInit, + options?: RelayOptions, ) { const url = typeof init === "string" ? init : init.url; - const config = { nbuffer: 64, modules: [], ...options }; - super(new LazyWebSocket(url), config); + super(new LazyWebSocket(url), options); this.config = { - url, + ...this.config, + modules: [], name: new URL(url).hostname, read: true, + url, write: true, - ...config, + ...options, }; this.ws.addEventListener( "message", @@ -76,36 +77,41 @@ export class Relay extends NostrNodeBase< return this.dispatch("message", message); }, ); + this.#ready = Promise.all( + this.config.modules.map((mod) => mod(this)), + ); } subscribe( filter: SubscriptionFilter | SubscriptionFilter[], options: Partial = {}, ): ReadableStream> { - options.realtime ??= true; - options.nbuffer ??= this.config.nbuffer; const context = { id: (options.id ?? crypto.randomUUID()) as SubscriptionId, filters: [filter].flat(), - options, + realtime: options.realtime ?? true, }; - const resubscribe = () => this.dispatch("resubscribe", { ...context }); - return new ReadableStream>({ - start: (controller) => { - this.addEventListener( - context.id, - () => this.ws.removeEventListener("close", resubscribe), - ); - this.dispatch("subscribe", { ...context, controller }); - }, - pull: () => { - this.ws.addEventListener("close", resubscribe, { once: true }); - this.ws.ready(); - }, - cancel: (reason) => { - this.dispatch("unsubscribe", { ...context, reason }); + const resubscribe = () => this.dispatch("resubscribe", context); + return new ReadableStream>( + { + start: async (controller) => { + await this.#ready; + this.addEventListener( + context.id, + () => this.ws.removeEventListener("close", resubscribe), + ); + this.dispatch("subscribe", { ...context, controller }); + }, + pull: () => { + this.ws.addEventListener("close", resubscribe, { once: true }); + this.ws.ready(); + }, + cancel: (reason) => { + this.dispatch("unsubscribe", { ...context, reason }); + }, }, - }, new CountQueuingStrategy({ highWaterMark: options.nbuffer })); + { highWaterMark: options.nbuffer ?? this.config.nbuffer }, + ); } /** @@ -114,8 +120,9 @@ export class Relay extends NostrNodeBase< * @throws {EventRejected} If the event is rejected by the relay * @throws {ConnectionClosed} If the WebSocket connection to the relay is closed */ - publish(event: NostrEvent): Promise { - return new Promise((resolve, reject) => { + async publish(event: NostrEvent): Promise { + await this.#ready; + return new Promise((resolve, reject) => { this.dispatch("publish", { event, resolve, reject }); this.ws.addEventListener( "close", @@ -126,10 +133,9 @@ export class Relay extends NostrNodeBase< } } -// ---------------------- +//------------- // RelayLikes -// ---------------------- - +//------------- export interface RelayLike extends Pick { readonly config: RelayLikeConfig; @@ -138,59 +144,12 @@ export interface RelayLike export type RelayLikeConfig = Pick; export type RelayLikeOptions = Partial; -// ---------------------- -// Events -// ---------------------- - -export interface SubscriptionContext { - id: SubscriptionId; - filters: SubscriptionFilter[]; - options: SubscriptionOptions; -} - -export interface SubscriptionContextWithController extends SubscriptionContext { - controller: ReadableStreamDefaultController; -} - -export interface SubscriptionContextWithReason extends SubscriptionContext { - reason: unknown; -} - -export interface PublicationContext { - event: NostrEvent; - resolve: () => void; - reject: (reason: unknown) => void; -} - -type SubscriptionMessage = { - [T in RelayToClientMessageType]: RelayToClientMessage[1] extends - SubscriptionId ? RelayToClientMessage : never; -}[RelayToClientMessageType]; - -type PublicationMessage = { - [T in RelayToClientMessageType]: RelayToClientMessage[1] extends EventId - ? RelayToClientMessage - : never; -}[RelayToClientMessageType]; - -export interface RelayEventTypeRecord { - message: RelayToClientMessage; - subscribe: SubscriptionContextWithController; - resubscribe: SubscriptionContext; - unsubscribe: SubscriptionContextWithReason; - publish: PublicationContext; - [id: SubscriptionId]: SubscriptionMessage; - [id: EventId]: PublicationMessage; -} - -export type RelayEventType = keyof RelayEventTypeRecord; - -// ---------------------- +//---------- // Modules -// ---------------------- - -export type RelayModule = NostrNodeModule< - ClientToRelayMessage, - RelayEventTypeRecord, - Relay ->; +//---------- +export interface RelayModule< + P extends NIP = never, // The NIP that the module provides + D extends NIP = never, // The NIPs that the module depends on +> { + (relay: Relay

): void | Promise; +} diff --git a/core/relays_test.ts b/core/relays_test.ts index c358940..ba01444 100644 --- a/core/relays_test.ts +++ b/core/relays_test.ts @@ -1,18 +1,29 @@ import { assert, assertEquals, assertObjectMatch } from "@std/assert"; -import { afterAll, beforeAll, describe, it } from "@std/testing/bdd"; +import { describe, it } from "@std/testing/bdd"; +import { assertType, Has } from "@std/testing/types"; +import "@lophus/nips"; +import { NIP } from "./protocol.ts"; import { Relay } from "./relays.ts"; -const url = "wss://localhost:8080"; +type HasNIP = T extends Relay ? Has : false; describe("Relay", () => { - let relay: Relay; + const url = "wss://localhost:8080"; - describe("constructed with url only", () => { - beforeAll(() => { - relay = new Relay(url); + describe("constructor", () => { + it("should not configure NIPs by default", () => { + const relay = new Relay(url); + assertType>(false); + }); + + it("should inherit the type of `nips` option", () => { + const relay = new Relay(url, { nips: [1] }); + assertType>(true); }); + }); - afterAll(() => relay.close()); + describe("constructed with url only", () => { + const relay = new Relay(url); it("should be constructable", () => { assert(relay instanceof Relay); @@ -39,17 +50,11 @@ describe("Relay", () => { }); describe("constructed with url and options", () => { - beforeAll(() => { - relay = new Relay(url, { - name: "test", - read: false, - write: false, - nbuffer: 20, - }); - }); - - afterAll(() => { - relay.close(); + const relay = new Relay(url, { + name: "test", + read: false, + write: false, + nbuffer: 20, }); it("should be constructable", () => { diff --git a/deno.json b/deno.json index 4178869..8d4dad2 100644 --- a/deno.json +++ b/deno.json @@ -8,10 +8,14 @@ "@lophus/lib/types": "./lib/types.ts", "@lophus/lib/testing": "./lib/testing.ts", "@lophus/lib/streams": "./lib/streams.ts", + "@lophus/core": "./core/mod.ts", "@lophus/core/clients": "./core/clients.ts", + "@lophus/core/nodes": "./core/nodes.ts", "@lophus/core/protocol": "./core/protocol.ts", "@lophus/core/relays": "./core/relays.ts", - "@lophus/nips": "./nips/mod.ts", + "@lophus/nips": "./nips/protocol.ts", + "@lophus/nips/clients": "./nips/clients.ts", + "@lophus/nips/relays": "./nips/relays.ts", "@lophus/nips/01": "./nips/01/protocol.ts", "@lophus/nips/02": "./nips/02/protocol.ts", "@lophus/nips/07": "./nips/07/protocol.ts", diff --git a/lib/types.ts b/lib/types.ts index 5f6a6fa..0936c4a 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -6,10 +6,10 @@ export type Brand = T & { __brand: B }; // ---------------------- // Promises // ---------------------- -export type PromiseCallbacks = { +export interface PromiseCallbackRecord { resolve: (value: T | PromiseLike) => void; reject: (reason?: unknown) => void; -}; +} // ---------------------- // Strings diff --git a/nips/01/clients.ts b/nips/01/clients.ts index f3458d9..4117ed7 100644 --- a/nips/01/clients.ts +++ b/nips/01/clients.ts @@ -14,7 +14,7 @@ declare module "@lophus/core/clients" { } } -export const install: ClientModule["install"] = (client) => { +const M: ClientModule<1> = (client) => { client.on("message", (message) => { switch (message[0]) { case "EVENT": { @@ -54,4 +54,4 @@ export const install: ClientModule["install"] = (client) => { }); }; -export default { install }; +export default M; diff --git a/nips/01/clients_test.ts b/nips/01/clients_test.ts index e01fd44..0758f90 100644 --- a/nips/01/clients_test.ts +++ b/nips/01/clients_test.ts @@ -8,17 +8,17 @@ import { SubscriptionId, } from "@lophus/core/protocol"; import { Client } from "@lophus/core/clients"; -import nip_01 from "./clients.ts"; +import mod from "./clients.ts"; describe("Client (NIP-01)", () => { let ws: MockWebSocket; - let client: Client; + let client: Client<1>; let subid: SubscriptionId; let sub: WritableStream; beforeAll(() => { ws = new MockWebSocket(); - client = new Client(ws, { modules: [nip_01] }); + client = new Client(ws, { modules: [mod] }); }); afterAll(() => { client.close(); diff --git a/nips/01/relays.ts b/nips/01/relays.ts index cce5861..74e6a06 100644 --- a/nips/01/relays.ts +++ b/nips/01/relays.ts @@ -1,8 +1,10 @@ -import { EventRejected, RelayModule } from "@lophus/core/relays"; +import { + EventRejected, + RelayModule, + SubscriptionClosed, +} from "@lophus/core/relays"; -export class SubscriptionClosed extends Error {} - -export const install: RelayModule["install"] = (relay) => { +export const M: RelayModule<1> = (relay) => { relay.on("message", (message) => { switch (message[0]) { case "EVENT": @@ -15,7 +17,7 @@ export const install: RelayModule["install"] = (relay) => { } }); - relay.on("subscribe", ({ id, filters, options, controller }) => { + relay.on("subscribe", ({ id, filters, realtime, controller }) => { relay.on(id, (message) => { switch (message[0]) { case "EVENT": { @@ -23,10 +25,10 @@ export const install: RelayModule["install"] = (relay) => { return controller.enqueue(event); } case "EOSE": { - if (!options.realtime) { - controller.close(); + if (realtime) { + return; } - break; + return controller.close(); } case "CLOSED": { return controller.error(new SubscriptionClosed(message[2])); @@ -62,4 +64,4 @@ export const install: RelayModule["install"] = (relay) => { }); }; -export default { install }; +export default M; diff --git a/nips/01/relays_test.ts b/nips/01/relays_test.ts index 93b7afd..6843715 100644 --- a/nips/01/relays_test.ts +++ b/nips/01/relays_test.ts @@ -1,17 +1,20 @@ import { assert, assertEquals, assertInstanceOf } from "@std/assert"; import { afterAll, beforeAll, describe, it } from "@std/testing/bdd"; import { MockWebSocket } from "@lophus/lib/testing"; -import { +import type { ClientToRelayMessage, EventId, NostrEvent, RelayToClientMessage, SubscriptionId, } from "@lophus/core/protocol"; -import { ConnectionClosed, EventRejected, Relay } from "@lophus/core/relays"; -import "./protocol.ts"; -import { SubscriptionClosed } from "./relays.ts"; -import nip_01 from "./relays.ts"; +import { + ConnectionClosed, + EventRejected, + Relay, + SubscriptionClosed, +} from "@lophus/core/relays"; +import mod from "./relays.ts"; function getRemoteSocket() { return MockWebSocket.instances[0].remote; @@ -19,13 +22,13 @@ function getRemoteSocket() { describe("Relay (NIP-01)", () => { const url = "wss://localhost:8080"; - let relay: Relay; + let relay: Relay<1>; let sub_0: ReadableStream>; let sub_1: ReadableStream>; beforeAll(() => { globalThis.WebSocket = MockWebSocket; - relay = new Relay(url, { modules: [nip_01] }); + relay = new Relay(url, { modules: [mod] }); }); afterAll(() => { diff --git a/nips/42/relays.ts b/nips/42/relays.ts index 8ce1383..270fb5f 100644 --- a/nips/42/relays.ts +++ b/nips/42/relays.ts @@ -2,7 +2,6 @@ import type { Stringified } from "@lophus/lib/types"; import type { EventInit } from "@lophus/core/protocol"; import type { RelayModule } from "@lophus/core/relays"; import type { Signer } from "@lophus/std/signs"; -import "./protocol.ts"; declare module "@lophus/core/relays" { interface RelayConfig { @@ -10,7 +9,7 @@ declare module "@lophus/core/relays" { } } -const install: RelayModule["install"] = (relay) => { +const M: RelayModule<42> = (relay) => { relay.on("message", (message) => { if (message[0] !== "AUTH") { // This NIP only handles AUTH messages @@ -32,4 +31,4 @@ const install: RelayModule["install"] = (relay) => { }); }; -export default { install }; +export default M; diff --git a/nips/_common.ts b/nips/_common.ts new file mode 100644 index 0000000..3aa04ca --- /dev/null +++ b/nips/_common.ts @@ -0,0 +1,9 @@ +import { NIP } from "@lophus/core/protocol"; + +/** + * Convert a NIP to a string. If the NIP is less than 10, a leading zero is + * added. + */ +export function nipToString(nip: NIP | number) { + return nip > 9 ? nip.toString() : "0" + nip.toString(); +} diff --git a/nips/clients.ts b/nips/clients.ts new file mode 100644 index 0000000..f68166c --- /dev/null +++ b/nips/clients.ts @@ -0,0 +1,15 @@ +import { NIP } from "@lophus/core/protocol"; +import { ClientModule } from "@lophus/core/clients"; +import "./protocol.ts"; +import { nipToString } from "./_common.ts"; + +const M: ClientModule = async (node) => { + await Promise.all(node.config.nips.map(async (nip) => { + const mod = await import( + new URL(`./${nipToString(nip)}/clients.ts`, import.meta.url).href + ) as ClientModule; + await mod(node); + })); +}; + +export default M; diff --git a/nips/deno.json b/nips/deno.json index 9fe69da..a890f95 100644 --- a/nips/deno.json +++ b/nips/deno.json @@ -2,7 +2,7 @@ "name": "@lophus/nips", "version": "0.0.14", "exports": { - ".": "./mod.ts", + ".": "./protocol.ts", "./01": "./01/protocol.ts", "./01/clients": "./01/clients.ts", "./01/relays": "./01/relays.ts", diff --git a/nips/mod.ts b/nips/protocol.ts similarity index 100% rename from nips/mod.ts rename to nips/protocol.ts diff --git a/nips/relays.ts b/nips/relays.ts new file mode 100644 index 0000000..3a0bf53 --- /dev/null +++ b/nips/relays.ts @@ -0,0 +1,15 @@ +import { NIP } from "@lophus/core/protocol"; +import { RelayModule } from "@lophus/core/relays"; +import "./protocol.ts"; +import { nipToString } from "./_common.ts"; + +const M: RelayModule = async (node) => { + await Promise.all(node.config.nips.map(async (nip) => { + const mod = await import( + new URL(`./${nipToString(nip)}/relays.ts`, import.meta.url).href + ) as RelayModule; + await mod(node); + })); +}; + +export default M;