From 45c93ed524af06e8cef2bc8f68737ba6587cc234 Mon Sep 17 00:00:00 2001 From: hasundue Date: Thu, 15 Feb 2024 11:37:08 +0900 Subject: [PATCH] refactor: simplify modular architecture (#27) --- core/clients.ts | 99 ++++++++--------------- core/nips.ts | 59 +++++++------- core/nodes.ts | 151 +++++++++++++---------------------- core/relays.ts | 172 +++++++++++++--------------------------- nips/01/clients.ts | 85 ++++++++++++-------- nips/01/clients_test.ts | 4 +- nips/01/relays.ts | 136 ++++++++++++++++--------------- nips/42/protocol.d.ts | 7 -- nips/42/relays.ts | 20 +++-- 9 files changed, 314 insertions(+), 419 deletions(-) diff --git a/core/clients.ts b/core/clients.ts index 4c8cbe5..4b78634 100644 --- a/core/clients.ts +++ b/core/clients.ts @@ -1,9 +1,7 @@ import type { ClientToRelayMessage, - ClientToRelayMessageType, NostrEvent, RelayToClientMessage, - SubscriptionFilter, SubscriptionId, } from "./protocol.d.ts"; import { @@ -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(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; /** * A class that represents a remote Nostr client. */ export class Client extends NostrNode< RelayToClientMessage, - EventDataTypeRecord, - ClientFunctionParameterTypeRecord + ClientEventTypeRecord > { declare ws: WebSocket; @@ -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) => { - // 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; -export type ClientOptions = Partial; - // ------------------------------ -// Functions +// Events // ------------------------------ -export type ClientModule = NostrNodeModule; - -type ClientFunctionParameterTypeRecord = { - [K in keyof _FunctionParameterTypeRecord]: - & _FunctionParameterTypeRecord[K] - & ClientFunctionContext; -}; - -type _FunctionParameterTypeRecord = { - "handleClientToRelayMessage": { - message: ClientToRelayMessage; - }; - "handleSubscriptionMessage": { - message: SubscriptionMessage; - controller: ReadableStreamDefaultController; - } & 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 {} // ------------------------------ -// Events +// Modules // ------------------------------ -type EventDataTypeRecord = { - [T in SubscriptionId]: SubscriptionMessage; -}; - -type SubscriptionMessage = { - [T in ClientToRelayMessageType]: ClientToRelayMessage[1] extends - SubscriptionId ? ClientToRelayMessage : never; -}[ClientToRelayMessageType]; - -export class ClientSubscriptionEvent extends NostrNodeEvent< - EventDataTypeRecord, - SubscriptionId -> { - constructor( - type: SubscriptionId, - init: MessageEventInit, - ) { - super(type, init); - } -} +export type ClientModule = NostrNodeModule< + RelayToClientMessage, + ClientEventTypeRecord, + Client +>; diff --git a/core/nips.ts b/core/nips.ts index e2f08e4..e3a7c1a 100644 --- a/core/nips.ts +++ b/core/nips.ts @@ -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>( - 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, - ) ?? [], - ); - }, -}; +/** + * 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 = NostrNode, +>( + 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>, + ) ?? [], + ); +} /** * Convert a NIP to a string. If the NIP is less than 10, a leading zero is diff --git a/core/nodes.ts b/core/nodes.ts index ff6fad7..4e47f9f 100644 --- a/core/nodes.ts +++ b/core/nodes.ts @@ -3,33 +3,33 @@ import type { Logger } from "./types.ts"; import { WebSocketLike } from "./websockets.ts"; export interface NostrNodeConfig< - F extends FunctionParameterTypeRecord = FunctionParameterTypeRecord, + W extends NostrMessage = NostrMessage, + R extends EventTypeRecord = EventTypeRecord, > { - modules: NostrNodeModule[]; + modules: NostrNodeModule[]; logger: Logger; nbuffer: number; } export type NostrNodeOptions< - F extends FunctionParameterTypeRecord = FunctionParameterTypeRecord, -> = Partial>; + W extends NostrMessage = NostrMessage, + R extends EventTypeRecord = EventTypeRecord, +> = Partial>; /** * Common base class for relays and clients. */ export class NostrNode< W extends NostrMessage = NostrMessage, - E extends EventDataTypeRecord = EventDataTypeRecord, - F extends FunctionParameterTypeRecord = FunctionParameterTypeRecord, + R extends EventTypeRecord = EventTypeRecord, > extends WritableStream implements EventTarget { readonly #eventTarget = new EventTarget(); - readonly config: Readonly>; - protected readonly functions: NostrNodeFunctionSet = {}; - protected readonly aborter = new AbortController(); + readonly #aborter = new AbortController(); + readonly config: Readonly>; constructor( readonly ws: WebSocketLike, - opts: NostrNodeOptions = {}, + opts: NostrNodeOptions = {}, ) { super({ write: (msg) => this.ws.send(JSON.stringify(msg)), @@ -48,7 +48,7 @@ export class NostrNode< } async close() { - this.aborter.abort(); + this.#aborter.abort(); try { await super.close(); } catch (err) { @@ -58,62 +58,41 @@ export class NostrNode< } } - addModule(module: NostrNodeModule) { - const functions = module.default; - for (const name in functions) { - this.addFunction(name, functions[name]!); - } - } - - addFunction>( - fname: K, - fn: FunctionType, - ): void { - const set = this.functions[fname] ?? (this.functions[fname] = new Set()); - set.add(fn); + addModule(module: NostrNodeModule) { + return module.default(this); } - async callFunction>( - fname: K, - context: F[K], - ): Promise { - const handlers = this.functions[fname]; - if (handlers) { - await Promise.all( - [...handlers].map((handler) => handler({ __fn__: fname, ...context })), - ); - } - } - - addEventListener = >( + addEventListener>( type: T, - listener: NostrNodeEventListenerOrEventListenerObject | null, + listener: + | NostrNodeEventListenerOrEventListenerObject + | null, options?: AddEventListenerOptions, - ) => { + ) { return this.#eventTarget.addEventListener( type, listener as EventListenerOrEventListenerObject, - { signal: this.aborter.signal, ...options }, + { signal: this.#aborter.signal, ...options }, ); - }; + } - removeEventListener = >( + removeEventListener>( type: T, - listener: NostrNodeEventListenerOrEventListenerObject | null, + listener: + | NostrNodeEventListenerOrEventListenerObject + | null, options?: boolean | EventListenerOptions, - ) => { + ) { return this.#eventTarget.removeEventListener( type, listener as EventListenerOrEventListenerObject, options, ); - }; + } - dispatchEvent = >( - event: NostrNodeEvent, - ) => { + dispatchEvent>(event: NostrNodeEvent) { return this.#eventTarget.dispatchEvent(event); - }; + } } // ------------------------------ @@ -121,74 +100,50 @@ export class NostrNode< // ------------------------------ export interface NostrNodeModule< - R extends FunctionParameterTypeRecord, + W extends NostrMessage = NostrMessage, + R extends EventTypeRecord = EventTypeRecord, + N extends NostrNode = NostrNode, > { - default: NostrNodeFunctions; + default(node: N): void; } -type NostrNodeFunctions< - R extends FunctionParameterTypeRecord = FunctionParameterTypeRecord, -> = { - [K in FunctionKey]?: FunctionType; -}; - // ------------------------------ -// Functions +// Events // ------------------------------ -type NostrNodeFunctionSet = Partial< - { - [K in FunctionKey]: Set>; - } ->; - -interface FunctionParameterTypeRecord { - [K: string]: FunctionParameterType; -} - // deno-lint-ignore no-empty-interface -interface FunctionParameterType {} +export interface EventTypeRecord {} -type FunctionKey = keyof R & string; +type EventType = keyof R & string; -type FunctionContextType< - R extends FunctionParameterTypeRecord, - K extends FunctionKey, -> = R[K] & { __fn__: K }; - -type FunctionType< - R extends FunctionParameterTypeRecord, - K extends FunctionKey, -> = (context: FunctionContextType) => void; - -// ------------------------------ -// Events -// ------------------------------ - -type EventDataTypeRecord = Record; - -type EventType = keyof R & string; +export abstract class NostrNodeEvent< + R extends EventTypeRecord, + T extends EventType, +> extends MessageEvent { + declare type: T; + constructor(type: T, data: R[T]) { + super(type, { data }); + } +} type NostrNodeEventListenerOrEventListenerObject< - R extends EventDataTypeRecord, + W extends NostrMessage, + R extends EventTypeRecord, T extends EventType, -> = NostrNodeEventListener | NostrNodeEventListenerObject; +> = NostrNodeEventListener | NostrNodeEventListenerObject; type NostrNodeEventListener< - R extends EventDataTypeRecord, + W extends NostrMessage, + R extends EventTypeRecord, T extends EventType, > // deno-lint-ignore no-explicit-any - = (this: NostrNode, ev: NostrNodeEvent) => any; + = (this: NostrNode, ev: MessageEvent) => any; type NostrNodeEventListenerObject< - R extends EventDataTypeRecord, + W extends NostrMessage, + R extends EventTypeRecord, T extends EventType, > = { // deno-lint-ignore no-explicit-any - handleEvent(this: NostrNode, ev: NostrNodeEvent): any; + handleEvent(this: NostrNode, ev: MessageEvent): any; }; - -export class NostrNodeEvent< - R extends EventDataTypeRecord, - T extends EventType, -> extends MessageEvent {} diff --git a/core/relays.ts b/core/relays.ts index c8d213a..dbd485f 100644 --- a/core/relays.ts +++ b/core/relays.ts @@ -1,11 +1,9 @@ import type { Stringified } from "./types.ts"; import type { ClientToRelayMessage, - EventId, EventKind, NostrEvent, RelayToClientMessage, - RelayToClientMessageType, RelayUrl, SubscriptionFilter, SubscriptionId, @@ -17,13 +15,17 @@ import { NostrNodeEvent, NostrNodeModule, } from "./nodes.ts"; -import { NIPs } from "./nips.ts"; +import { importNips } from "./nips.ts"; // ---------------------- // NIPs // ---------------------- -const nips = await NIPs.import(import.meta.url, "../nips"); +const NIPs = await importNips< + ClientToRelayMessage, + RelayEventTypeRecord, + Relay +>(import.meta.url, "../nips"); // ---------------------- // Errors @@ -37,7 +39,7 @@ export class ConnectionClosed extends Error {} // ---------------------- export interface RelayConfig - extends NostrNodeConfig { + extends NostrNodeConfig { url: RelayUrl; name: string; read: boolean; @@ -61,8 +63,7 @@ export interface SubscriptionOptions { */ export class Relay extends NostrNode< ClientToRelayMessage, - EventDataTypeRecord, - RelayFunctionParameterTypeRecord + RelayEventTypeRecord > { declare ws: LazyWebSocket; readonly config: Readonly; @@ -76,7 +77,7 @@ export class Relay extends NostrNode< nbuffer: 10, logger: {}, ...options, - modules: nips.concat(options?.modules ?? []), + modules: NIPs.concat(options?.modules ?? []), }; super(new LazyWebSocket(url), config); this.config = { @@ -89,12 +90,9 @@ export class Relay extends NostrNode< this.ws.addEventListener( "message", (ev: MessageEvent>) => { - // TODO: Validate message. const message = JSON.parse(ev.data) as RelayToClientMessage; - return this.callFunction("handleRelayToClientMessage", { - message, - relay: this, - }); + // TODO: Validate the message. + return this.dispatchEvent(new RelayEvent("message", message)); }, ); } @@ -109,23 +107,20 @@ export class Relay extends NostrNode< id: (options.id ?? crypto.randomUUID()) as SubscriptionId, filters: [filter].flat(), options, - relay: this, }; return new ReadableStream>({ start: (controller) => { - this.addEventListener( - context.id, - (ev) => - this.callFunction("handleSubscriptionMessage", { - message: ev.data, - controller, - ...context, - }), + this.dispatchEvent( + new RelayEvent("subscribe", { ...context, controller }), ); - this.callFunction("startSubscription", { controller, ...context }); + }, + pull: (controller) => { + this.dispatchEvent(new RelayEvent("pull", { ...context, controller })); }, cancel: (reason) => { - return this.callFunction("closeSubscription", { reason, ...context }); + this.dispatchEvent( + new RelayEvent("unsubscribe", { ...context, reason }), + ); }, }, new CountQueuingStrategy({ highWaterMark: options.nbuffer })); } @@ -137,18 +132,9 @@ export class Relay extends NostrNode< * @throws {ConnectionClosed} If the WebSocket connection to the relay is closed */ publish(event: NostrEvent): Promise { - // We await the response instead of awaiting this - this.callFunction("publishEvent", { event, relay: this }); - return new Promise((resolve, reject) => { - this.addEventListener( - event.id, - (ev) => - resolve(this.callFunction("handlePublicationMessage", { - message: ev.data, - event, - relay: this, - })), + this.dispatchEvent( + new RelayEvent("publish", { event, resolve, reject }), ); this.ws.addEventListener( "close", @@ -170,101 +156,53 @@ export interface RelayLike extends WritableStream { publish: Relay["publish"]; } -export type RelayLikeConfig = Omit< - RelayConfig, - "url" | keyof NostrNodeConfig ->; - +export type RelayLikeConfig = Pick; export type RelayLikeOptions = Partial; // ---------------------- // Events // ---------------------- -type EventDataTypeRecord = - & { - [T in SubscriptionId]: SubscriptionMessage; - } - & { - [T in EventId]: PublicationMessage; - }; - -export class RelaySubscriptionEvent extends NostrNodeEvent< - EventDataTypeRecord, - SubscriptionId -> { - constructor( - type: SubscriptionId, - init: MessageEventInit, - ) { - super(type, init); - } +export interface SubscriptionContext { + id: SubscriptionId; + filters: SubscriptionFilter[]; + options: SubscriptionOptions; } -export class PublicationEvent extends NostrNodeEvent< - EventDataTypeRecord, - EventId -> { - constructor( - type: EventId, - init: MessageEventInit, - ) { - super(type, init); - } +export interface SubscriptionContextWithController extends SubscriptionContext { + controller: ReadableStreamDefaultController; } -// ---------------------- -// Functions -// ---------------------- - -export type RelayModule = NostrNodeModule; - -type RelayFunctionParameterTypeRecord = { - [K in keyof FunctionParameterTypeRecord]: - & FunctionParameterTypeRecord[K] - & RelayFunctionContext; -}; - -type FunctionParameterTypeRecord = { - "handleRelayToClientMessage": { - message: RelayToClientMessage; - }; - "handleSubscriptionMessage": { - message: SubscriptionMessage; - controller: ReadableStreamDefaultController; - } & SubscriptionContext; - "handlePublicationMessage": { - message: PublicationMessage; - event: NostrEvent; - }; - "publishEvent": { - event: NostrEvent; - }; - "startSubscription": { - controller: ReadableStreamDefaultController; - } & SubscriptionContext; - "closeSubscription": { - reason: unknown; - } & SubscriptionContext; -}; +export interface SubscriptionContextWithReason extends SubscriptionContext { + reason: unknown; +} -interface RelayFunctionContext { - relay: Relay; +export interface PublicationContext { + event: NostrEvent; + resolve: () => void; + reject: (reason: unknown) => void; } -interface SubscriptionContext { - id: SubscriptionId; - filters: SubscriptionFilter[]; - options: SubscriptionOptions; +export interface RelayEventTypeRecord { + message: RelayToClientMessage; + subscribe: SubscriptionContextWithController; + pull: SubscriptionContextWithController; + unsubscribe: SubscriptionContextWithReason; + publish: PublicationContext; } -type SubscriptionMessage = { - [T in RelayToClientMessageType]: RelayToClientMessage[1] extends - SubscriptionId ? RelayToClientMessage : never; -}[RelayToClientMessageType]; +export type RelayEventType = keyof RelayEventTypeRecord; + +export class RelayEvent< + T extends RelayEventType = RelayEventType, +> extends NostrNodeEvent {} + +// ---------------------- +// Modules +// ---------------------- -type PublicationMessage = { - [T in RelayToClientMessageType]: RelayToClientMessage[1] extends EventId - ? RelayToClientMessage - : never; -}[RelayToClientMessageType]; +export type RelayModule = NostrNodeModule< + ClientToRelayMessage, + RelayEventTypeRecord, + Relay +>; diff --git a/nips/01/clients.ts b/nips/01/clients.ts index a5e0511..840c729 100644 --- a/nips/01/clients.ts +++ b/nips/01/clients.ts @@ -1,38 +1,57 @@ import type { NostrEvent } from "../../core/protocol.d.ts"; -import { ClientModule, ClientSubscriptionEvent } from "../../core/clients.ts"; +import { ClientModule } from "../../core/clients.ts"; -export default { - async handleClientToRelayMessage({ message, client }) { - const kind = message[0]; - if (kind === "EVENT") { - const event = message[1]; +interface EventValidationContext { + data: NostrEvent; + resolve: (value: unknown) => void; + // deno-lint-ignore no-explicit-any + reject: (reason?: any) => void; +} - // This should throw if the event is not acceptable. - await client.callFunction("acceptEvent", { event, client }); - return client.send(["OK", event.id, true, ""]); - } - if (kind === "CLOSE") { - const sid = message[1]; - const sub = client.subscriptions.get(sid); - if (!sub) { - return client.send(["NOTICE", `Unknown subscription ID: ${sid}`]); +declare module "../../core/clients.ts" { + interface ClientEventTypeRecord { + validate: EventValidationContext; + } +} + +const install: ClientModule["default"] = (client) => { + client.addEventListener("message", ({ data: message }) => { + switch (message[0]) { + case "EVENT": { + const event = message[1]; + /** try { + await new Promise((resolve, reject) => { + client.dispatchEvent( + new ClientEvent("validate", { data: event, resolve, reject }), + ); + }); + } catch (err) { + return client.send(["OK", event.id, false, err.message]); + } */ + return client.send(["OK", event.id, true, ""]); + } + case "REQ": { + const id = message[1]; + return client.subscriptions.set( + id, + new WritableStream({ + write(event) { + return client.send(["EVENT", id, event]); + }, + }), + ); + } + case "CLOSE": { + const id = message[1]; + const sub = client.subscriptions.get(id); + if (!sub) { + return client.send(["NOTICE", `Unknown subscription ID: ${id}`]); + } + client.subscriptions.delete(id); + return sub.close(); } - client.subscriptions.delete(sid); - return sub.close(); - } - if (kind === "REQ") { - const sid = message[1]; - client.subscriptions.set( - sid, - new WritableStream({ - write(event) { - return client.send(["EVENT", sid, event]); - }, - }), - ); - return client.dispatchEvent( - new ClientSubscriptionEvent(sid, { data: message }), - ); } - }, -} satisfies ClientModule["default"]; + }); +}; + +export default install; diff --git a/nips/01/clients_test.ts b/nips/01/clients_test.ts index 6fd7103..35f2fc2 100644 --- a/nips/01/clients_test.ts +++ b/nips/01/clients_test.ts @@ -31,7 +31,7 @@ describe("NIP-01/Client", () => { it("should receive an event and send a OK message", async () => { const event = { id: "test-ok", kind: 0 }; const received = new Promise>((resolve) => { - client.addFunction("handleClientToRelayMessage", ({ message }) => { + client.addEventListener("message", ({ data: message }) => { if (message[0] === "EVENT") resolve(message); }); }); @@ -49,7 +49,7 @@ describe("NIP-01/Client", () => { subid = "test-req" as SubscriptionId; const request: ClientToRelayMessage<"REQ"> = ["REQ", subid, { kinds: [1] }]; const received = new Promise>((resolve) => { - client.addFunction("handleClientToRelayMessage", ({ message }) => { + client.addEventListener("message", ({ data: message }) => { if (message[0] === "REQ" && message[1] === subid) resolve(message); }); }); diff --git a/nips/01/relays.ts b/nips/01/relays.ts index 9907054..c00f682 100644 --- a/nips/01/relays.ts +++ b/nips/01/relays.ts @@ -1,75 +1,85 @@ -import { - EventRejected, - PublicationEvent, - RelayModule, - RelaySubscriptionEvent, -} from "../../core/relays.ts"; +import type { + EventId, + RelayToClientMessage, + RelayToClientMessageType, + SubscriptionId, +} from "../../core/protocol.d.ts"; +import { EventRejected, RelayEvent, RelayModule } from "../../core/relays.ts"; -export default { - handleRelayToClientMessage({ message, relay }) { - const type = message[0]; - relay.config.logger?.debug?.(relay.config.name, message); - switch (type) { +type SubscriptionMessage = { + [T in RelayToClientMessageType]: RelayToClientMessage[1] extends + SubscriptionId ? RelayToClientMessage : never; +}[RelayToClientMessageType]; + +type PublicationMessage = { + [T in RelayToClientMessageType]: RelayToClientMessage[1] extends EventId + ? RelayToClientMessage + : never; +}[RelayToClientMessageType]; + +type ExtentionalEventTypeRecord = + & { + [id in SubscriptionId]: SubscriptionMessage; + } + & { + [id in EventId]: PublicationMessage; + }; + +declare module "../../core/relays.ts" { + // deno-lint-ignore no-empty-interface + interface RelayEventTypeRecord extends ExtentionalEventTypeRecord {} +} + +const install: RelayModule["default"] = (relay) => { + relay.addEventListener("message", ({ data: message }) => { + switch (message[0]) { case "EVENT": - case "EOSE": { - const sid = message[1]; - return relay.dispatchEvent( - new RelaySubscriptionEvent(sid, { data: message }), - ); - } + case "EOSE": case "OK": { - const eid = message[1]; - return relay.dispatchEvent( - new PublicationEvent(eid, { data: message }), - ); + return relay.dispatchEvent(new RelayEvent(message[1], message)); } case "NOTICE": { - const notice = message[1]; - return relay.config?.logger?.info?.(notice); + return relay.config?.logger?.info?.(message[1]); } } - }, - - handleSubscriptionMessage({ message, options, controller }) { - const type = message[0]; - switch (type) { - case "EVENT": { - const [, , event] = message; - return controller.enqueue(event); - } - case "EOSE": { - if (!options.realtime) { - return controller.close(); + }); + relay.addEventListener("subscribe", (ev) => { + const { id, filters, options, controller } = ev.data; + relay.addEventListener(id, ({ data: message }) => { + switch (message[0]) { + case "EVENT": { + const [, , event] = message; + return controller.enqueue(event); + } + case "EOSE": { + if (!options.realtime) { + return controller.close(); + } } } - } - }, - - handlePublicationMessage({ message, event }) { - const type = message[0]; - if (type !== "OK") { - // This NIP only supports OK messages. - return; - } - const accepted = message[2]; - if (accepted) { - return; - } - const reason = message[3]; - throw new EventRejected(reason, { cause: event }); - }, - - publishEvent({ event, relay }) { - return relay.send(["EVENT", event]); - }, - - startSubscription({ filters, id, relay }) { + }); return relay.send(["REQ", id, ...filters]); - }, - - closeSubscription({ id, relay }) { - if (relay.ws.readyState === WebSocket.OPEN) { + }); + relay.addEventListener("unsubscribe", ({ data: { id } }) => { + if (relay.status === WebSocket.OPEN) { return relay.send(["CLOSE", id]); } - }, -} satisfies RelayModule["default"]; + }); + relay.addEventListener("publish", (ev) => { + const { event, resolve, reject } = ev.data; + relay.addEventListener(event.id, ({ data: message }) => { + if (message[0] !== "OK") { + // This NIP only supports OK messages. + return; + } + const [, , accepted, reason] = message; + if (accepted) { + return resolve(); + } + reject(new EventRejected(reason, { cause: event })); + }); + return relay.send(["EVENT", event]); + }); +}; + +export default install; diff --git a/nips/42/protocol.d.ts b/nips/42/protocol.d.ts index fdbfc5b..97397f9 100644 --- a/nips/42/protocol.d.ts +++ b/nips/42/protocol.d.ts @@ -1,6 +1,5 @@ import "../../core/protocol.d.ts"; import "../../core/relays.ts"; -import type { Signer } from "../../lib/signs.ts"; declare module "../../core/protocol.d.ts" { interface NipRecord { @@ -29,9 +28,3 @@ declare module "../../core/protocol.d.ts" { "challenge": [string]; } } - -declare module "../../core/relays.ts" { - interface RelayConfig { - signer?: Signer; - } -} diff --git a/nips/42/relays.ts b/nips/42/relays.ts index c1cc686..3ecbbb6 100644 --- a/nips/42/relays.ts +++ b/nips/42/relays.ts @@ -1,12 +1,18 @@ import type { Stringified } from "../../core/types.ts"; import type { RelayModule } from "../../core/relays.ts"; import type { EventInit } from "../../lib/events.ts"; +import type { Signer } from "../../lib/signs.ts"; import "./protocol.d.ts"; -export default { - handleRelayToClientMessage({ message, relay }) { - const type = message[0]; - if (type !== "AUTH") { +declare module "../../core/relays.ts" { + interface RelayConfig { + signer?: Signer; + } +} + +const install: RelayModule["default"] = (relay) => { + relay.addEventListener("message", ({ data: message }) => { + if (message[0] !== "AUTH") { // This NIP only handles AUTH messages return; } @@ -23,5 +29,7 @@ export default { content: "" as Stringified<"">, }; return relay.publish(relay.config.signer.sign(event)); - }, -} satisfies RelayModule["default"]; + }); +}; + +export default install;