Skip to content

Commit

Permalink
refactor: abandon enums for NIPs and event kinds in favor of type rec…
Browse files Browse the repository at this point in the history
…ords (#18)
  • Loading branch information
hasundue authored Oct 17, 2023
1 parent fb0f320 commit f28c0f8
Show file tree
Hide file tree
Showing 18 changed files with 117 additions and 52 deletions.
12 changes: 3 additions & 9 deletions core/clients.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,13 @@ import {
NostrNodeEvent,
NostrNodeModule,
} from "./nodes.ts";
import { NIPs } from "./nips.ts";

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

const NIPs = await Promise.all(
new URL(import.meta.url).searchParams.get("nips")?.split(",").map(Number).map(
(nip) =>
import(
new URL(`../nips/${nip}/clients.ts`, import.meta.url).href
) as Promise<ClientModule>,
) ?? [],
);
const nips = await NIPs.import<ClientModule>(import.meta.url, "../nips");

/**
* A class that represents a remote Nostr client.
Expand All @@ -47,7 +41,7 @@ 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.
Expand Down
38 changes: 38 additions & 0 deletions core/nips.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { NIP } from "./protocol.d.ts";
import { 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>,
) ?? [],
);
},
};

/**
* Convert a NIP to a string. If the NIP is less than 10, a leading zero is
* added.
*/
function nipToString(nip: NIP | number) {
return nip > 9 ? nip.toString() : "0" + nip.toString();
}
4 changes: 3 additions & 1 deletion core/nodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,9 @@ export class NostrNode<
// Extensions
// ------------------------------

export interface NostrNodeModule<R extends FunctionParameterTypeRecord> {
export interface NostrNodeModule<
R extends FunctionParameterTypeRecord,
> {
default: NostrNodeFunctions<R>;
}

Expand Down
29 changes: 21 additions & 8 deletions core/protocol.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// deno-lint-ignore-file no-empty-enum no-empty-interface
// deno-lint-ignore-file no-empty-interface

/**
* Implementation of unextendable part of NIP-01 (Nostr basic protocol):
Expand All @@ -15,11 +15,9 @@ import type { AlphabetLetter, Brand, Stringified } from "./types.ts";
// Extendable interfaces
// ----------------------

export enum NIP {}
export enum EventKind {}
export interface NipRecord {}

export interface EventKindRecord
extends Record<EventKind, EventKindRecordEntry> {}
export interface EventKindRecord {}

export interface TagRecord {}

Expand Down Expand Up @@ -65,7 +63,7 @@ export type EventSerializePrecursor<K extends EventKind = EventKind> = [
// Tags
// ----------------------

export type TagType = keyof TagRecord;
export type TagType = keyof TagRecord & string;
export type TagParam = string | undefined;

export type Tag<T extends TagType = TagType> = {
Expand Down Expand Up @@ -150,6 +148,8 @@ export type SubscriptionFilter<
// Events
// ----------------------

export type EventKind = keyof EventKindRecord & number;

export interface EventKindRecordEntry {
Tag: Tag;
Content: unknown;
Expand All @@ -158,8 +158,8 @@ export interface EventKindRecordEntry {

export type EventContent = EventContentFor<EventKind>;

export type EventContentFor<K extends EventKind> =
EventKindRecord[K]["Content"];
export type EventContentFor<K extends EventKind> = EventKindRecord[K] extends
EventKindRecordEntry ? EventKindRecord[K]["Content"] : never;

export type ResponsePrefixFor<K extends EventKind = EventKind> =
EventKindRecord[K] extends { ResponsePrefix: infer P extends string } ? P
Expand All @@ -172,3 +172,16 @@ export type ParameterizedReplaceableEventKind = Brand<
EventKind,
"ParameterizedReplaceable"
>;

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

export type NIP = keyof NipRecord & number;

export interface NipRecordEntry {
ClientToRelayMessage: ClientToRelayMessageType;
RelayToClientMessage: RelayToClientMessageType;
EventKind: EventKind;
Tag: TagType;
}
12 changes: 3 additions & 9 deletions core/relays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,13 @@ import {
NostrNodeEvent,
NostrNodeModule,
} from "./nodes.ts";
import { NIPs } from "./nips.ts";

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

const NIPs = await Promise.all(
new URL(import.meta.url).searchParams.get("nips")?.split(",").map(Number).map(
(nip) =>
import(
new URL(`../nips/${nip}/relays.ts`, import.meta.url).href
) as Promise<RelayModule>,
) ?? [],
);
const nips = await NIPs.import<RelayModule>(import.meta.url, "../nips");

// ----------------------
// Errors
Expand Down Expand Up @@ -89,7 +83,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 = {
Expand Down
4 changes: 3 additions & 1 deletion lib/signs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import type {
NostrEvent,
Signature,
} from "../core/protocol.d.ts";
import type { UnsignedEvent } from "../nips/07/protocol.d.ts";
import { bytesToHex, schnorr, sha256 } from "./x/noble.ts";
import type { EventInit } from "./events.ts";
import { Timestamp } from "./times.ts";
import { UnsignedEvent } from "../nips/7/protocol.d.ts";

export type { UnsignedEvent } from "../nips/07/protocol.d.ts";

export type PrivateKey = Brand<string, "PrivateKey">;

Expand Down
14 changes: 11 additions & 3 deletions lib/signs_test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { Stringified } from "../core/types.ts";
import "../nips/01/protocol.d.ts";
import { describe, it } from "../lib/std/testing.ts";
import { assert, assertEquals } from "../lib/std/assert.ts";
import { Timestamp } from "../lib/times.ts";
import { PrivateKey, PublicKey, Signer, Verifier } from "./signs.ts";
import {
PrivateKey,
PublicKey,
Signer,
UnsignedEvent,
Verifier,
} from "./signs.ts";

describe("PrivateKey", () => {
it("generates a private key", () => {
Expand All @@ -21,12 +29,12 @@ describe("PublicKey", () => {
describe("Signer/Verifier", () => {
const nsec = PrivateKey.generate();
const signer = new Signer(nsec);
const event = {
const event: UnsignedEvent = {
pubkey: PublicKey.from(nsec),
created_at: Timestamp.now,
kind: 1,
tags: [],
content: "lophus",
content: "lophus" as Stringified<string>,
};
const verifier = new Verifier();

Expand Down
File renamed without changes.
File renamed without changes.
13 changes: 7 additions & 6 deletions nips/1/protocol.d.ts → nips/01/protocol.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ import "../../core/protocol.d.ts";
import type { Url } from "../../core/types.ts";

declare module "../../core/protocol.d.ts" {
enum NIP {
BasicProtocol = 1,
}
enum EventKind {
Metadata = 0,
TextNote = 1,
interface NipRecord {
1: {
ClientToRelayMessage: "EVENT" | "REQ" | "CLOSE";
RelayToClientMessage: "EVENT" | "OK" | "EOSE" | "NOTICE";
EventKind: 0 | 1;
Tag: "e" | "p" | "a" | "d";
};
}
interface TagRecord {
/** Event ID */
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
12 changes: 6 additions & 6 deletions nips/2/protocol.d.ts → nips/02/protocol.d.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import "../../core/protocol.d.ts";

declare module "../../core/protocol.d.ts" {
enum NIP {
ContactList = 2,
}
enum EventKind {
ContactList = 3,
interface NipRecord {
2: {
Tag: "p";
};
}
interface EventKindRecord {
3: {
Tag: ["p", PublicKey, RelayUrl, string];
Tag: ContactTag;
Content: "";
};
}
type ContactTag = ["p", PublicKey, RelayUrl, petname: string];
}
File renamed without changes.
9 changes: 8 additions & 1 deletion nips/7/protocol.d.ts → nips/07/protocol.d.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { EventKind, NostrEvent, PublicKey } from "../../core/protocol.d.ts";
import type { Optional } from "../../lib/types.ts";

declare module "../../core/protocol.d.ts" {
interface NipRecord {
7: Record<string, never>;
}
}

/**
* An event that has not been signed.
*/
export type UnsignedEvent<K extends EventKind = EventKind> = Omit<
export type UnsignedEvent<K extends EventKind = EventKind> = Optional<
NostrEvent<K>,
"id" | "pubkey" | "sig"
>;
Expand Down
10 changes: 7 additions & 3 deletions nips/7/signs.ts → nips/07/signs.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { Stringified } from "../../core/types.ts";
import { EventContent, EventKind, NostrEvent } from "../../core/protocol.d.ts";
import {
EventContentFor,
EventKind,
NostrEvent,
} from "../../core/protocol.d.ts";
import { EventInit } from "../../lib/events.ts";
import { Timestamp } from "../../lib/times.ts";
import { UnsignedEvent } from "./protocol.d.ts";
Expand All @@ -23,8 +27,8 @@ export class Signer extends TransformStream<EventInit, NostrEvent> {
created_at: Timestamp.now,
tags: [],
...init,
content: JSON.stringify(init.content) as Stringified<EventContent>,
} satisfies UnsignedEvent;
content: JSON.stringify(init.content) as Stringified<EventContentFor<K>>,
} satisfies UnsignedEvent<K>;
return window.nostr!.signEvent(unsigned);
}
}
12 changes: 7 additions & 5 deletions nips/42/protocol.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import "../../core/relays.ts";
import type { Signer } from "../../lib/signs.ts";

declare module "../../core/protocol.d.ts" {
enum NIP {
Authentication = 42,
}
enum EventKind {
ClientAuth = 22242,
interface NipRecord {
42: {
ClientToRelayMessage: "AUTH";
RelayToClientMessage: "AUTH";
EventKind: 22242;
Tag: Tag<"relay" | "challenge">;
};
}
interface RelayToClientMessageRecord {
AUTH: [challenge: string];
Expand Down

0 comments on commit f28c0f8

Please sign in to comment.