From 459f3b98de6775965bcab15d4078a49a6b77ca39 Mon Sep 17 00:00:00 2001 From: ennmichael Date: Thu, 2 Mar 2023 22:02:17 +0100 Subject: [PATCH 1/3] prefer string literals to enums when possible --- packages/nostr/src/client/conn.ts | 31 +++++++++++----------------- packages/nostr/src/client/emitter.ts | 2 +- packages/nostr/src/client/index.ts | 14 ++++++------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/packages/nostr/src/client/conn.ts b/packages/nostr/src/client/conn.ts index 3019710..145f88f 100644 --- a/packages/nostr/src/client/conn.ts +++ b/packages/nostr/src/client/conn.ts @@ -106,16 +106,13 @@ export class Conn { */ export type IncomingMessage = IncomingEvent | IncomingNotice -export const enum IncomingKind { - Event, - Notice, -} +export type IncomingKind = "event" | "notice" /** * Incoming "EVENT" message. */ export interface IncomingEvent { - kind: IncomingKind.Event + kind: "event" subscriptionId: SubscriptionId signed: SignedEvent raw: RawEvent @@ -125,7 +122,7 @@ export interface IncomingEvent { * Incoming "NOTICE" message. */ export interface IncomingNotice { - kind: IncomingKind.Notice + kind: "notice" notice: string } @@ -137,17 +134,13 @@ export type OutgoingMessage = | OutgoingOpenSubscription | OutgoingCloseSubscription -export const enum OutgoingKind { - Event, - OpenSubscription, - CloseSubscription, -} +export type OutgoingKind = "event" | "openSubscription" | "closeSubscription" /** * Outgoing "EVENT" message. */ export interface OutgoingEvent { - kind: OutgoingKind.Event + kind: "event" event: SignedEvent | RawEvent } @@ -155,7 +148,7 @@ export interface OutgoingEvent { * Outgoing "REQ" message, which opens a subscription. */ export interface OutgoingOpenSubscription { - kind: OutgoingKind.OpenSubscription + kind: "openSubscription" id: SubscriptionId filters: Filters[] } @@ -164,7 +157,7 @@ export interface OutgoingOpenSubscription { * Outgoing "CLOSE" message, which closes a subscription. */ export interface OutgoingCloseSubscription { - kind: OutgoingKind.CloseSubscription + kind: "closeSubscription" id: SubscriptionId } @@ -203,7 +196,7 @@ async function parseIncomingMessage(data: string): Promise { } const raw = parseEventData(json[2]) return { - kind: IncomingKind.Event, + kind: "event", subscriptionId: new SubscriptionId(json[1]), signed: await SignedEvent.verify(raw), raw, @@ -216,7 +209,7 @@ async function parseIncomingMessage(data: string): Promise { ) } return { - kind: IncomingKind.Notice, + kind: "notice", notice: json[1], } } @@ -224,17 +217,17 @@ async function parseIncomingMessage(data: string): Promise { } function serializeOutgoingMessage(msg: OutgoingMessage): string { - if (msg.kind === OutgoingKind.Event) { + if (msg.kind === "event") { const raw = msg.event instanceof SignedEvent ? msg.event.serialize() : msg.event return JSON.stringify(["EVENT", raw]) - } else if (msg.kind === OutgoingKind.OpenSubscription) { + } else if (msg.kind === "openSubscription") { return JSON.stringify([ "REQ", msg.id.toString(), ...serializeFilters(msg.filters), ]) - } else if (msg.kind === OutgoingKind.CloseSubscription) { + } else if (msg.kind === "closeSubscription") { return JSON.stringify(["CLOSE", msg.id.toString()]) } else { throw new Error(`invalid message: ${JSON.stringify(msg)}`) diff --git a/packages/nostr/src/client/emitter.ts b/packages/nostr/src/client/emitter.ts index 3c98021..f209c0b 100644 --- a/packages/nostr/src/client/emitter.ts +++ b/packages/nostr/src/client/emitter.ts @@ -44,7 +44,7 @@ export class EventEmitter extends Base { override off(eventName: "event", listener: EventListener): this override off(eventName: "notice", listener: NoticeListener): this override off(eventName: "error", listener: ErrorListener): this - override off(eventName: string, listener: Listener): this { + override off(eventName: EventName, listener: Listener): this { return super.off(eventName, listener) } diff --git a/packages/nostr/src/client/index.ts b/packages/nostr/src/client/index.ts index 48be246..328d590 100644 --- a/packages/nostr/src/client/index.ts +++ b/packages/nostr/src/client/index.ts @@ -1,7 +1,7 @@ import { ProtocolError } from "../error" import { EventId, Event, EventKind, SignedEvent, RawEvent } from "../event" import { PrivateKey, PublicKey } from "../keypair" -import { Conn, IncomingKind, OutgoingKind } from "./conn" +import { Conn } from "./conn" import * as secp from "@noble/secp256k1" import { EventEmitter } from "./emitter" @@ -52,7 +52,7 @@ export class Nostr extends EventEmitter { // Handle messages on this connection. conn.on("message", async (msg) => { try { - if (msg.kind === IncomingKind.Event) { + if (msg.kind === "event") { this.emit( "event", { @@ -62,7 +62,7 @@ export class Nostr extends EventEmitter { }, this ) - } else if (msg.kind === IncomingKind.Notice) { + } else if (msg.kind === "notice") { this.emit("notice", msg.notice, this) } else { throw new ProtocolError(`invalid message ${msg}`) @@ -81,7 +81,7 @@ export class Nostr extends EventEmitter { for (const [key, filters] of this.#subscriptions.entries()) { const subscriptionId = new SubscriptionId(key) conn.send({ - kind: OutgoingKind.OpenSubscription, + kind: "openSubscription", id: subscriptionId, filters, }) @@ -145,7 +145,7 @@ export class Nostr extends EventEmitter { continue } conn.send({ - kind: OutgoingKind.OpenSubscription, + kind: "openSubscription", id: subscriptionId, filters, }) @@ -167,7 +167,7 @@ export class Nostr extends EventEmitter { continue } conn.send({ - kind: OutgoingKind.CloseSubscription, + kind: "closeSubscription", id: subscriptionId, }) } @@ -210,7 +210,7 @@ export class Nostr extends EventEmitter { event = await SignedEvent.sign(event, key) } conn.send({ - kind: OutgoingKind.Event, + kind: "event", event, }) } From 3c78f740e0c10a731ffc1cf6208eaf9c01696054 Mon Sep 17 00:00:00 2001 From: ennmichael Date: Thu, 2 Mar 2023 22:36:31 +0100 Subject: [PATCH 2/3] implement nip20 ok at the connection level --- packages/nostr/src/client/conn.ts | 171 +++++++++++--------- packages/nostr/src/client/index.ts | 52 +++--- packages/nostr/src/event.ts | 3 +- packages/nostr/test/simple-communication.ts | 3 + 4 files changed, 129 insertions(+), 100 deletions(-) diff --git a/packages/nostr/src/client/conn.ts b/packages/nostr/src/client/conn.ts index 145f88f..15963cc 100644 --- a/packages/nostr/src/client/conn.ts +++ b/packages/nostr/src/client/conn.ts @@ -1,6 +1,6 @@ import { ProtocolError } from "../error" import { Filters, SubscriptionId } from "." -import { RawEvent, SignedEvent } from "../event" +import { EventId, RawEvent, SignedEvent } from "../event" import WebSocket from "ws" import { unixTimestamp } from "../util" @@ -14,7 +14,6 @@ import { unixTimestamp } from "../util" */ export class Conn { readonly #socket: WebSocket - /** * Messages which were requested to be sent before the websocket was ready. * Once the websocket becomes ready, these messages will be sent and cleared. @@ -23,15 +22,25 @@ export class Conn { // before NIP-44 auth. The legacy code reuses the same array for these two but I think they should be // different, and the NIP-44 stuff should be handled by Nostr. #pending: OutgoingMessage[] = [] - - #msgCallback?: IncomingMessageCallback - #errorCallback?: ErrorCallback + /** + * Callback for errors. + */ + readonly #onError: (err: unknown) => void get url(): string { return this.#socket.url } - constructor(endpoint: string | URL) { + constructor({ + endpoint, + onMessage, + onError, + }: { + endpoint: string | URL + onMessage: (msg: IncomingMessage) => void + onError: (err: unknown) => void + }) { + this.#onError = onError this.#socket = new WebSocket(endpoint) // Handle incoming messages. @@ -40,14 +49,14 @@ export class Conn { // Validate and parse the message. if (typeof value !== "string") { const err = new ProtocolError(`invalid message data: ${value}`) - this.#errorCallback?.(err) + onError(err) return } try { - const msg = await parseIncomingMessage(value) - this.#msgCallback?.(msg) + const msg = await Conn.#parseIncomingMessage(value) + onMessage(msg) } catch (err) { - this.#errorCallback?.(err) + onError(err) } }) @@ -60,22 +69,10 @@ export class Conn { }) this.#socket.addEventListener("error", (err) => { - this.#errorCallback?.(err) + onError(err) }) } - on(on: "message", cb: IncomingMessageCallback): void - on(on: "error", cb: ErrorCallback): void - on(on: "message" | "error", cb: IncomingMessageCallback | ErrorCallback) { - if (on === "message") { - this.#msgCallback = cb as IncomingMessageCallback - } else if (on === "error") { - this.#errorCallback = cb as ErrorCallback - } else { - throw new Error(`unexpected input: ${on}`) - } - } - send(msg: OutgoingMessage): void { if (this.#socket.readyState < WebSocket.OPEN) { this.#pending.push(msg) @@ -84,11 +81,11 @@ export class Conn { try { this.#socket.send(serializeOutgoingMessage(msg), (err) => { if (err !== undefined && err !== null) { - this.#errorCallback?.(err) + this.#onError?.(err) } }) } catch (err) { - this.#errorCallback?.(err) + this.#onError?.(err) } } @@ -96,17 +93,81 @@ export class Conn { try { this.#socket.close() } catch (err) { - this.#errorCallback?.(err) + this.#onError?.(err) } } + + static async #parseIncomingMessage(data: string): Promise { + const json = parseJson(data) + if (!(json instanceof Array)) { + throw new ProtocolError(`incoming message is not an array: ${data}`) + } + if (json.length === 0) { + throw new ProtocolError(`incoming message is an empty array: ${data}`) + } + if (json[0] === "EVENT") { + if (typeof json[1] !== "string") { + throw new ProtocolError( + `second element of "EVENT" should be a string, but wasn't: ${data}` + ) + } + if (typeof json[2] !== "object") { + throw new ProtocolError( + `second element of "EVENT" should be an object, but wasn't: ${data}` + ) + } + const raw = parseEventData(json[2]) + return { + kind: "event", + subscriptionId: new SubscriptionId(json[1]), + signed: await SignedEvent.verify(raw), + raw, + } + } + if (json[0] === "NOTICE") { + if (typeof json[1] !== "string") { + throw new ProtocolError( + `second element of "NOTICE" should be a string, but wasn't: ${data}` + ) + } + return { + kind: "notice", + notice: json[1], + } + } + if (json[0] === "OK") { + if (typeof json[1] !== "string") { + throw new ProtocolError( + `second element of "OK" should be a string, but wasn't: ${data}` + ) + } + if (typeof json[2] !== "boolean") { + throw new ProtocolError( + `third element of "OK" should be a boolean, but wasn't: ${data}` + ) + } + if (typeof json[3] !== "string") { + throw new ProtocolError( + `fourth element of "OK" should be a string, but wasn't: ${data}` + ) + } + return { + kind: "ok", + eventId: new EventId(json[1]), + ok: json[2], + message: json[3], + } + } + throw new ProtocolError(`unknown incoming message: ${data}`) + } } /** * A message sent from a relay to the client. */ -export type IncomingMessage = IncomingEvent | IncomingNotice +export type IncomingMessage = IncomingEvent | IncomingNotice | IncomingOk -export type IncomingKind = "event" | "notice" +export type IncomingKind = "event" | "notice" | "ok" /** * Incoming "EVENT" message. @@ -126,6 +187,16 @@ export interface IncomingNotice { notice: string } +/** + * Incoming "OK" message. + */ +export interface IncomingOk { + kind: "ok" + eventId: EventId + ok: boolean + message: string +} + /** * A message sent from the client to a relay. */ @@ -161,9 +232,6 @@ export interface OutgoingCloseSubscription { id: SubscriptionId } -type IncomingMessageCallback = (message: IncomingMessage) => unknown -type ErrorCallback = (error: unknown) => unknown - interface RawFilters { ids?: string[] authors?: string[] @@ -175,47 +243,6 @@ interface RawFilters { limit?: number } -async function parseIncomingMessage(data: string): Promise { - const json = parseJson(data) - if (!(json instanceof Array)) { - throw new ProtocolError(`incoming message is not an array: ${data}`) - } - if (json.length === 0) { - throw new ProtocolError(`incoming message is an empty array: ${data}`) - } - if (json[0] === "EVENT") { - if (typeof json[1] !== "string") { - throw new ProtocolError( - `second element of "EVENT" should be a string, but wasn't: ${data}` - ) - } - if (typeof json[2] !== "object") { - throw new ProtocolError( - `second element of "EVENT" should be an object, but wasn't: ${data}` - ) - } - const raw = parseEventData(json[2]) - return { - kind: "event", - subscriptionId: new SubscriptionId(json[1]), - signed: await SignedEvent.verify(raw), - raw, - } - } - if (json[0] === "NOTICE") { - if (typeof json[1] !== "string") { - throw new ProtocolError( - `second element of "NOTICE" should be a string, but wasn't: ${data}` - ) - } - return { - kind: "notice", - notice: json[1], - } - } - throw new ProtocolError(`unknown incoming message: ${data}`) -} - function serializeOutgoingMessage(msg: OutgoingMessage): string { if (msg.kind === "event") { const raw = diff --git a/packages/nostr/src/client/index.ts b/packages/nostr/src/client/index.ts index 328d590..e6af7bc 100644 --- a/packages/nostr/src/client/index.ts +++ b/packages/nostr/src/client/index.ts @@ -47,34 +47,32 @@ export class Nostr extends EventEmitter { } // If there is no existing connection, open a new one. - const conn = new Conn(url) - - // Handle messages on this connection. - conn.on("message", async (msg) => { - try { - if (msg.kind === "event") { - this.emit( - "event", - { - signed: msg.signed, - subscriptionId: msg.subscriptionId, - raw: msg.raw, - }, - this - ) - } else if (msg.kind === "notice") { - this.emit("notice", msg.notice, this) - } else { - throw new ProtocolError(`invalid message ${msg}`) + const conn = new Conn({ + endpoint: url, + // Handle messages on this connection. + onMessage: (msg) => { + try { + if (msg.kind === "event") { + this.emit( + "event", + { + signed: msg.signed, + subscriptionId: msg.subscriptionId, + raw: msg.raw, + }, + this + ) + } else if (msg.kind === "notice") { + this.emit("notice", msg.notice, this) + } else { + throw new ProtocolError(`invalid message ${msg}`) + } + } catch (err) { + this.emit("error", err, this) } - } catch (err) { - this.emit("error", err, this) - } - }) - - // Forward connection errors to the error callbacks. - conn.on("error", (err) => { - this.emit("error", err, this) + }, + // Forward errors on this connection. + onError: (err) => this.emit("error", err, this), }) // Resend existing subscriptions to this connection. diff --git a/packages/nostr/src/event.ts b/packages/nostr/src/event.ts index 5dd4e02..8dcfb29 100644 --- a/packages/nostr/src/event.ts +++ b/packages/nostr/src/event.ts @@ -87,7 +87,8 @@ export class EventId { } } - private constructor(hex: string) { + constructor(hex: string) { + // TODO Validate that this is 32-byte hex this.#hex = hex } diff --git a/packages/nostr/test/simple-communication.ts b/packages/nostr/test/simple-communication.ts index 86c6618..078a898 100644 --- a/packages/nostr/test/simple-communication.ts +++ b/packages/nostr/test/simple-communication.ts @@ -34,6 +34,9 @@ describe("single event communication", function () { // for future events. subscriber.off("event", listener) + subscriber.on("error", done) + publisher.on("error", done) + publisher.close() subscriber.close() From 191ec8d7f087ef778f93a44dddf45b5042294484 Mon Sep 17 00:00:00 2001 From: ennmichael Date: Thu, 2 Mar 2023 23:47:49 +0100 Subject: [PATCH 3/3] allow the client to react to ok --- packages/nostr/src/client/conn.ts | 6 +- packages/nostr/src/client/emitter.ts | 39 ++++++++++- packages/nostr/src/client/index.ts | 22 ++++--- packages/nostr/test/simple-communication.ts | 71 ++++++++++++++------- 4 files changed, 102 insertions(+), 36 deletions(-) diff --git a/packages/nostr/src/client/conn.ts b/packages/nostr/src/client/conn.ts index 15963cc..8b24b0b 100644 --- a/packages/nostr/src/client/conn.ts +++ b/packages/nostr/src/client/conn.ts @@ -32,16 +32,16 @@ export class Conn { } constructor({ - endpoint, + url, onMessage, onError, }: { - endpoint: string | URL + url: URL onMessage: (msg: IncomingMessage) => void onError: (err: unknown) => void }) { this.#onError = onError - this.#socket = new WebSocket(endpoint) + this.#socket = new WebSocket(url) // Handle incoming messages. this.#socket.addEventListener("message", async (msgData) => { diff --git a/packages/nostr/src/client/emitter.ts b/packages/nostr/src/client/emitter.ts index f209c0b..2c9eb78 100644 --- a/packages/nostr/src/client/emitter.ts +++ b/packages/nostr/src/client/emitter.ts @@ -1,5 +1,6 @@ import Base from "events" -import { EventParams, Nostr } from "." +import { Nostr, SubscriptionId } from "." +import { EventId, RawEvent, SignedEvent } from "../event" /** * Overrides providing better types for EventEmitter methods. @@ -11,6 +12,7 @@ export class EventEmitter extends Base { listener: RemoveListener ): this override addListener(eventName: "notice", listener: NoticeListener): this + override addListener(eventName: "ok", listener: OkListener): this override addListener(eventName: "error", listener: ErrorListener): this override addListener(eventName: "newListener", listener: ErrorListener): this override addListener(eventName: EventName, listener: Listener): this { @@ -21,6 +23,7 @@ export class EventEmitter extends Base { override emit(eventName: "removeListener", listener: RemoveListener): boolean override emit(eventName: "event", params: EventParams, nostr: Nostr): boolean override emit(eventName: "notice", notice: string, nostr: Nostr): boolean + override emit(eventName: "ok", params: OkParams, nostr: Nostr): boolean override emit(eventName: "error", err: unknown, nostr: Nostr): boolean override emit(eventName: EventName, ...args: unknown[]): boolean { return super.emit(eventName, ...args) @@ -34,6 +37,7 @@ export class EventEmitter extends Base { override listeners(eventName: "removeListener"): EventListener[] override listeners(eventName: "event"): EventListener[] override listeners(eventName: "notice"): NoticeListener[] + override listeners(eventName: "ok"): OkListener[] override listeners(eventName: "error"): ErrorListener[] override listeners(eventName: EventName): Listener[] { return super.listeners(eventName) as Listener[] @@ -43,6 +47,7 @@ export class EventEmitter extends Base { override off(eventName: "removeListener", listener: RemoveListener): this override off(eventName: "event", listener: EventListener): this override off(eventName: "notice", listener: NoticeListener): this + override off(eventName: "ok", listener: OkListener): this override off(eventName: "error", listener: ErrorListener): this override off(eventName: EventName, listener: Listener): this { return super.off(eventName, listener) @@ -52,6 +57,7 @@ export class EventEmitter extends Base { override on(eventName: "removeListener", listener: RemoveListener): this override on(eventName: "event", listener: EventListener): this override on(eventName: "notice", listener: NoticeListener): this + override on(eventName: "ok", listener: OkListener): this override on(eventName: "error", listener: ErrorListener): this override on(eventName: EventName, listener: Listener): this { return super.on(eventName, listener) @@ -61,6 +67,7 @@ export class EventEmitter extends Base { override once(eventName: "removeListener", listener: RemoveListener): this override once(eventName: "event", listener: EventListener): this override once(eventName: "notice", listener: NoticeListener): this + override once(eventName: "ok", listener: OkListener): this override once(eventName: "error", listener: ErrorListener): this override once(eventName: EventName, listener: Listener): this { return super.once(eventName, listener) @@ -76,6 +83,7 @@ export class EventEmitter extends Base { ): this override prependListener(eventName: "event", listener: EventListener): this override prependListener(eventName: "notice", listener: NoticeListener): this + override prependListener(eventName: "ok", listener: OkListener): this override prependListener(eventName: "error", listener: ErrorListener): this override prependListener(eventName: EventName, listener: Listener): this { return super.prependListener(eventName, listener) @@ -97,6 +105,7 @@ export class EventEmitter extends Base { eventName: "notice", listener: NoticeListener ): this + override prependOnceListener(eventName: "ok", listener: OkListener): this override prependOnceListener( eventName: "error", listener: ErrorListener @@ -116,6 +125,7 @@ export class EventEmitter extends Base { ): this override removeListener(eventName: "event", listener: EventListener): this override removeListener(eventName: "notice", listener: NoticeListener): this + override removeListener(eventName: "ok", listener: OkListener): this override removeListener(eventName: "error", listener: ErrorListener): this override removeListener(eventName: EventName, listener: Listener): this { return super.removeListener(eventName, listener) @@ -131,15 +141,40 @@ export class EventEmitter extends Base { // TODO Also add on: ("subscribed", subscriptionId) which checks "OK"/"NOTICE" and makes a callback? // TODO Also add on: ("ok", boolean, eventId) which checks "OK"/"NOTICE" and makes a callback? -type EventName = "newListener" | "removeListener" | "event" | "notice" | "error" +type EventName = + | "newListener" + | "removeListener" + | "event" + | "notice" + | "ok" + | "error" + type NewListener = (eventName: EventName, listener: Listener) => void type RemoveListener = (eventName: EventName, listener: Listener) => void type EventListener = (params: EventParams, nostr: Nostr) => void +type OkListener = (params: OkParams, nostr: Nostr) => void type NoticeListener = (notice: string, nostr: Nostr) => void type ErrorListener = (error: unknown, nostr: Nostr) => void + type Listener = | NewListener | RemoveListener | EventListener | NoticeListener + | OkListener | ErrorListener + +// TODO Document this +export interface EventParams { + signed: SignedEvent + subscriptionId: SubscriptionId + raw: RawEvent +} + +// TODO Document this +export interface OkParams { + eventId: EventId + relay: URL + ok: boolean + message: string +} diff --git a/packages/nostr/src/client/index.ts b/packages/nostr/src/client/index.ts index e6af7bc..d83e189 100644 --- a/packages/nostr/src/client/index.ts +++ b/packages/nostr/src/client/index.ts @@ -46,9 +46,11 @@ export class Nostr extends EventEmitter { return } + const connUrl = new URL(url) + // If there is no existing connection, open a new one. const conn = new Conn({ - endpoint: url, + url: connUrl, // Handle messages on this connection. onMessage: (msg) => { try { @@ -64,6 +66,17 @@ export class Nostr extends EventEmitter { ) } else if (msg.kind === "notice") { this.emit("notice", msg.notice, this) + } else if (msg.kind === "ok") { + this.emit( + "ok", + { + eventId: msg.eventId, + relay: connUrl, + ok: msg.ok, + message: msg.message, + }, + this + ) } else { throw new ProtocolError(`invalid message ${msg}`) } @@ -287,10 +300,3 @@ export interface Filters { until?: Date limit?: number } - -// TODO Document this -export interface EventParams { - signed: SignedEvent - subscriptionId: SubscriptionId - raw: RawEvent -} diff --git a/packages/nostr/test/simple-communication.ts b/packages/nostr/test/simple-communication.ts index 078a898..4841a6e 100644 --- a/packages/nostr/test/simple-communication.ts +++ b/packages/nostr/test/simple-communication.ts @@ -1,26 +1,37 @@ -import { EventParams, Nostr } from "../src/client" -import { EventKind } from "../src/event" +import { Nostr } from "../src/client" +import { EventKind, SignedEvent } from "../src/event" import { PrivateKey } from "../src/keypair" import assert from "assert" +import { EventParams } from "../src/client/emitter" // TODO Switch out the relay implementation and see if the issue persists +// TODO Do on("error", done) for all of these -describe("single event communication", function () { - it("ok", function (done) { - const secret = new PrivateKey( - "nsec1xlu55y6fqfgrq448xslt6a8j2rh7lj08hyhgs94ryq04yf6surwsjl0kzh" - ) - const pubkey = secret.pubkey +describe("simple communication", function () { + const secret = new PrivateKey( + "nsec1xlu55y6fqfgrq448xslt6a8j2rh7lj08hyhgs94ryq04yf6surwsjl0kzh" + ) + const pubkey = secret.pubkey + const timestamp = new Date() + const note = "hello world" + const url = new URL("ws://localhost:12648") - const timestamp = new Date() - const note = "hello world" + const publisher = new Nostr() + const subscriber = new Nostr() - const publisher = new Nostr() - publisher.open("ws://localhost:12648") - const subscriber = new Nostr() - subscriber.open("ws://localhost:12648") + beforeEach(() => { + publisher.open(url) + subscriber.open(url) + }) - function listener({ signed: { event } }: EventParams) { + afterEach(() => { + publisher.close() + subscriber.close() + }) + + it("publish and receive", function (done) { + function listener({ signed: { event } }: EventParams, nostr: Nostr) { + assert.equal(nostr, subscriber) assert.equal(event.kind, EventKind.TextNote) assert.equal(event.pubkey.toString(), pubkey.toString()) assert.equal(event.createdAt.toString(), timestamp.toString()) @@ -34,19 +45,11 @@ describe("single event communication", function () { // for future events. subscriber.off("event", listener) - subscriber.on("error", done) - publisher.on("error", done) - - publisher.close() - subscriber.close() - done() } subscriber.on("event", listener) - subscriber.subscribe([]) - publisher.publish( { kind: EventKind.TextNote, @@ -57,4 +60,26 @@ describe("single event communication", function () { secret ) }) + + it("publish and ok", function (done) { + SignedEvent.sign( + { + kind: EventKind.TextNote, + createdAt: timestamp, + content: note, + pubkey, + }, + secret + ).then((event) => { + publisher.on("ok", (params, nostr) => { + assert.equal(nostr, publisher) + assert.equal(params.eventId.toString(), event.eventId.toString()) + assert.equal(params.relay.toString(), url.toString()) + assert.equal(params.ok, true) + done() + }) + publisher.on("error", done) + publisher.publish(event) + }) + }) })