Merge pull request #474 from v0l/nostr-package-nip05-dns-identifiers
`nostr` package: implement NIP-05
This commit is contained in:
commit
25c1f48a95
@ -321,4 +321,4 @@
|
||||
"zjJZBd": "You're ready!",
|
||||
"zonsdq": "Failed to load LNURL service",
|
||||
"zvCDao": "Automatically show latest notes"
|
||||
}
|
||||
}
|
||||
|
2
packages/nostr/.prettierignore
Normal file
2
packages/nostr/.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
dist/
|
||||
src/legacy
|
@ -9,33 +9,33 @@ A strongly-typed nostr client for Node and the browser.
|
||||
The goal of the project is to have all of the following implemented
|
||||
and tested against a real-world relay implementation.
|
||||
|
||||
_Progress: 7/34 (20%)._
|
||||
_Progress: 8/34 (23%)._
|
||||
|
||||
- [X] NIP-01: Basic protocol flow description
|
||||
- [X] NIP-02: Contact List and Petnames
|
||||
- [x] NIP-01: Basic protocol flow description
|
||||
- [x] NIP-02: Contact List and Petnames
|
||||
- [ ] NIP-03: OpenTimestamps Attestations for Events
|
||||
- [X] NIP-04: Encrypted Direct Message
|
||||
- [ ] NIP-05: Mapping Nostr keys to DNS-based internet identifiers
|
||||
- [x] NIP-04: Encrypted Direct Message
|
||||
- [x] NIP-05: Mapping Nostr keys to DNS-based internet identifiers
|
||||
- [ ] NIP-06: Basic key derivation from mnemonic seed phrase
|
||||
- [ ] NIP-07: window.nostr capability for web browsers
|
||||
- [ ] NIP-08: Handling Mentions
|
||||
- [ ] NIP-09: Event Deletion
|
||||
- [ ] NIP-10: Conventions for clients' use of `e` and `p` tags in text events
|
||||
- TODO Check if this applies
|
||||
- [X] NIP-11: Relay Information Document
|
||||
- [X] NIP-12: Generic Tag Queries
|
||||
- [x] NIP-11: Relay Information Document
|
||||
- [x] NIP-12: Generic Tag Queries
|
||||
- [ ] NIP-13: Proof of Work
|
||||
- [ ] NIP-14: Subject tag in text events
|
||||
- [X] NIP-15: End of Stored Events Notice
|
||||
- [x] NIP-15: End of Stored Events Notice
|
||||
- [ ] NIP-19: bech32-encoded entities
|
||||
- [X] `npub`
|
||||
- [X] `nsec`
|
||||
- [x] `npub`
|
||||
- [x] `nsec`
|
||||
- [ ] `note`, `nprofile`, `nevent`, `nrelay`, `naddr`
|
||||
- [X] NIP-20: Command Results
|
||||
- [x] NIP-20: Command Results
|
||||
- [ ] NIP-21: `nostr:` URL scheme
|
||||
- [ ] NIP-23: Long-form Content
|
||||
- [ ] NIP-25: Reactions
|
||||
- [ ] NIP-26: Delegated Event Signing
|
||||
- [ ] NIP-27: Text Note References
|
||||
- [ ] NIP-28: Public Chat
|
||||
- [ ] NIP-36: Sensitive Content
|
||||
- [ ] NIP-39: External Identities in Profiles
|
||||
|
@ -1,7 +1,12 @@
|
||||
version: "3.1"
|
||||
services:
|
||||
well-known:
|
||||
build: ./docker/well-known
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 12647:80
|
||||
relay:
|
||||
build: ./relay
|
||||
build: ./docker/relay
|
||||
restart: on-failure
|
||||
ports:
|
||||
- 12648:8080
|
||||
|
@ -9,4 +9,4 @@ EXPOSE 8000
|
||||
COPY . .
|
||||
USER $APP_USER
|
||||
RUN yarn
|
||||
CMD yarn app /bin/bash -c "rm -rf /usr/src/app/db/* && ./nostr-rs-relay --db /usr/src/app/db --config ./config.toml"
|
||||
CMD ["/bin/bash", "-c", "while :; do yarn app /bin/bash -c 'rm -rf /usr/src/app/db/* && ./nostr-rs-relay --db /usr/src/app/db --config ./config.toml'; done;"]
|
4
packages/nostr/docker/well-known/Dockerfile
Normal file
4
packages/nostr/docker/well-known/Dockerfile
Normal file
@ -0,0 +1,4 @@
|
||||
# An nginx server to return a .well-known/nostr.json file for testing.
|
||||
FROM nginx
|
||||
RUN mkdir /usr/share/nginx/html/.well-known
|
||||
COPY nostr.json /usr/share/nginx/html/.well-known/nostr.json
|
10
packages/nostr/docker/well-known/nostr.json
Normal file
10
packages/nostr/docker/well-known/nostr.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"names": {
|
||||
"bob": "be4be3c0c4f2d18f11215087d7d18fad2c9e0269fc50e19d526bdfb11a522a64"
|
||||
},
|
||||
"relays": {
|
||||
"be4be3c0c4f2d18f11215087d7d18fad2c9e0269fc50e19d526bdfb11a522a64": [
|
||||
"ws://example.com"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
import { EventKind, RawEvent, Unsigned } from "."
|
||||
import { EventKind, RawEvent, signEvent } from "."
|
||||
import { NostrError } from "../common"
|
||||
import { PublicKey } from "../crypto"
|
||||
import { HexOrBechPrivateKey, PublicKey } from "../crypto"
|
||||
|
||||
/**
|
||||
* Contact list event.
|
||||
@ -28,18 +28,24 @@ export interface Contact {
|
||||
/**
|
||||
* Create a contact list event.
|
||||
*/
|
||||
export function createContactList(contacts: Contact[]): Unsigned<ContactList> {
|
||||
return {
|
||||
kind: EventKind.ContactList,
|
||||
tags: contacts.map((contact) => [
|
||||
"p",
|
||||
contact.pubkey,
|
||||
contact.relay?.toString() ?? "",
|
||||
contact.petname ?? "",
|
||||
]),
|
||||
content: "",
|
||||
getContacts,
|
||||
}
|
||||
export function createContactList(
|
||||
contacts: Contact[],
|
||||
priv?: HexOrBechPrivateKey
|
||||
): Promise<ContactList> {
|
||||
return signEvent(
|
||||
{
|
||||
kind: EventKind.ContactList,
|
||||
tags: contacts.map((contact) => [
|
||||
"p",
|
||||
contact.pubkey,
|
||||
contact.relay?.toString() ?? "",
|
||||
contact.petname ?? "",
|
||||
]),
|
||||
content: "",
|
||||
getContacts,
|
||||
},
|
||||
priv
|
||||
)
|
||||
}
|
||||
|
||||
export function getContacts(this: ContactList): Contact[] {
|
||||
|
@ -1,12 +1,5 @@
|
||||
import {
|
||||
EventId,
|
||||
EventKind,
|
||||
RawEvent,
|
||||
signEvent,
|
||||
Unsigned,
|
||||
UnsignedWithPubkey,
|
||||
} from "."
|
||||
import { defined, NostrError } from "../common"
|
||||
import { EventId, EventKind, RawEvent, signEvent } from "."
|
||||
import { NostrError } from "../common"
|
||||
import {
|
||||
aesDecryptBase64,
|
||||
aesEncryptBase64,
|
||||
@ -77,7 +70,7 @@ export async function createDirectMessage(
|
||||
}
|
||||
|
||||
export async function getMessage(
|
||||
this: UnsignedWithPubkey<DirectMessage>,
|
||||
this: DirectMessage,
|
||||
priv?: HexOrBechPrivateKey
|
||||
): Promise<string | undefined> {
|
||||
if (priv !== undefined) {
|
||||
@ -96,7 +89,7 @@ export async function getMessage(
|
||||
return undefined
|
||||
}
|
||||
|
||||
export function getRecipient(this: Unsigned<RawEvent>): PublicKey {
|
||||
export function getRecipient(this: DirectMessage): PublicKey {
|
||||
const recipientTag = this.tags.find((tag) => tag[0] === "p")
|
||||
if (typeof recipientTag?.[1] !== "string") {
|
||||
throw new NostrError(
|
||||
@ -108,7 +101,7 @@ export function getRecipient(this: Unsigned<RawEvent>): PublicKey {
|
||||
return recipientTag[1]
|
||||
}
|
||||
|
||||
export function getPrevious(this: Unsigned<RawEvent>): EventId | undefined {
|
||||
export function getPrevious(this: DirectMessage): EventId | undefined {
|
||||
const previousTag = this.tags.find((tag) => tag[0] === "e")
|
||||
if (previousTag === undefined) {
|
||||
return undefined
|
||||
|
@ -9,7 +9,11 @@ import {
|
||||
} from "../crypto"
|
||||
import { Timestamp, unixTimestamp, NostrError } from "../common"
|
||||
import { TextNote } from "./text"
|
||||
import { getUserMetadata, SetMetadata } from "./set-metadata"
|
||||
import {
|
||||
getUserMetadata,
|
||||
SetMetadata,
|
||||
verifyInternetIdentifier,
|
||||
} from "./set-metadata"
|
||||
import {
|
||||
DirectMessage,
|
||||
getMessage,
|
||||
@ -94,11 +98,10 @@ export type Unsigned<T extends Event | RawEvent> = {
|
||||
pubkey?: PublicKey
|
||||
}
|
||||
|
||||
// TODO This doesn't need to be exposed by the lib
|
||||
/**
|
||||
* Same as @see {@link Unsigned}, but with the pubkey field.
|
||||
*/
|
||||
export type UnsignedWithPubkey<T extends Event | RawEvent> = {
|
||||
type UnsignedWithPubkey<T extends Event | RawEvent> = {
|
||||
[Property in keyof T as Exclude<
|
||||
Property,
|
||||
"id" | "sig" | "created_at"
|
||||
@ -113,7 +116,7 @@ export type UnsignedWithPubkey<T extends Event | RawEvent> = {
|
||||
* Add the "id," "sig," and "pubkey" fields to the event. Set "created_at" to the current timestamp
|
||||
* if missing. Return the event.
|
||||
*/
|
||||
export async function signEvent<T extends Event | RawEvent>(
|
||||
export async function signEvent<T extends RawEvent>(
|
||||
event: Unsigned<T>,
|
||||
priv?: HexOrBechPrivateKey
|
||||
): Promise<T> {
|
||||
@ -164,6 +167,7 @@ export async function parseEvent(event: RawEvent): Promise<Event> {
|
||||
...event,
|
||||
kind: EventKind.SetMetadata,
|
||||
getUserMetadata,
|
||||
verifyInternetIdentifier,
|
||||
}
|
||||
}
|
||||
|
||||
@ -194,14 +198,14 @@ export async function parseEvent(event: RawEvent): Promise<Event> {
|
||||
async function serializeEventId(
|
||||
event: UnsignedWithPubkey<RawEvent>
|
||||
): Promise<EventId> {
|
||||
// It's not defined whether JSON.stringify produces a string with whitespace stripped.
|
||||
// Building the JSON string manually as follows ensures that there's no whitespace.
|
||||
// In hindsight using JSON as a data format for hashing and signing is not the best
|
||||
// design decision.
|
||||
const serializedTags = `[${event.tags
|
||||
.map((tag) => `[${tag.map((v) => `"${v}"`).join(",")}]`)
|
||||
.join(",")}]`
|
||||
const serialized = `[0,"${event.pubkey}",${event.created_at},${event.kind},${serializedTags},"${event.content}"]`
|
||||
const serialized = JSON.stringify([
|
||||
0,
|
||||
event.pubkey,
|
||||
event.created_at,
|
||||
event.kind,
|
||||
event.tags,
|
||||
event.content,
|
||||
])
|
||||
return await sha256(Uint8Array.from(charCodes(serialized)))
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { EventKind, RawEvent, Unsigned } from "."
|
||||
import { EventKind, RawEvent, signEvent } from "."
|
||||
import { NostrError, parseJson } from "../common"
|
||||
import { HexOrBechPrivateKey } from "../crypto"
|
||||
|
||||
/**
|
||||
* Set metadata event. Used for disseminating use profile information.
|
||||
@ -13,29 +14,45 @@ export interface SetMetadata extends RawEvent {
|
||||
* Get the user metadata specified in this event.
|
||||
*/
|
||||
getUserMetadata(): UserMetadata
|
||||
/**
|
||||
* Verify the NIP-05 DNS-based internet identifier associated with the user metadata.
|
||||
* Throws if the internet identifier is invalid or fails verification.
|
||||
* @param pubkey The public key to use if the event does not specify a pubkey. If the event
|
||||
* does specify a pubkey
|
||||
* @return The internet identifier. `undefined` if there is no internet identifier.
|
||||
*/
|
||||
verifyInternetIdentifier(
|
||||
opts?: VerificationOptions
|
||||
): Promise<InternetIdentifier | undefined>
|
||||
}
|
||||
|
||||
export interface UserMetadata {
|
||||
name: string
|
||||
about: string
|
||||
picture: string
|
||||
nip05?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a set metadata event.
|
||||
*/
|
||||
export function createSetMetadata(
|
||||
content: UserMetadata
|
||||
): Unsigned<SetMetadata> {
|
||||
return {
|
||||
kind: EventKind.SetMetadata,
|
||||
tags: [],
|
||||
content: JSON.stringify(content),
|
||||
getUserMetadata,
|
||||
}
|
||||
content: UserMetadata,
|
||||
priv?: HexOrBechPrivateKey
|
||||
): Promise<SetMetadata> {
|
||||
return signEvent(
|
||||
{
|
||||
kind: EventKind.SetMetadata,
|
||||
tags: [],
|
||||
content: JSON.stringify(content),
|
||||
getUserMetadata,
|
||||
verifyInternetIdentifier,
|
||||
},
|
||||
priv
|
||||
)
|
||||
}
|
||||
|
||||
export function getUserMetadata(this: Unsigned<RawEvent>): UserMetadata {
|
||||
export function getUserMetadata(this: SetMetadata): UserMetadata {
|
||||
const userMetadata = parseJson(this.content)
|
||||
if (
|
||||
typeof userMetadata.name !== "string" ||
|
||||
@ -48,3 +65,59 @@ export function getUserMetadata(this: Unsigned<RawEvent>): UserMetadata {
|
||||
}
|
||||
return userMetadata
|
||||
}
|
||||
|
||||
export async function verifyInternetIdentifier(
|
||||
this: SetMetadata,
|
||||
opts?: VerificationOptions
|
||||
): Promise<InternetIdentifier | undefined> {
|
||||
const metadata = this.getUserMetadata()
|
||||
if (metadata.nip05 === undefined) {
|
||||
return undefined
|
||||
}
|
||||
const [name, domain] = metadata.nip05.split("@")
|
||||
if (
|
||||
name === undefined ||
|
||||
domain === undefined ||
|
||||
!/^[a-zA-Z0-9-_]+$/.test(name)
|
||||
) {
|
||||
throw new NostrError(
|
||||
`invalid NIP-05 internet identifier: ${metadata.nip05}`
|
||||
)
|
||||
}
|
||||
const res = await fetch(
|
||||
`${
|
||||
opts?.https === false ? "http" : "https"
|
||||
}://${domain}/.well-known/nostr.json?name=${name}`,
|
||||
{ redirect: "error" }
|
||||
)
|
||||
const wellKnown = await res.json()
|
||||
const pubkey = wellKnown.names?.[name]
|
||||
if (pubkey !== this.pubkey) {
|
||||
throw new NostrError(
|
||||
`invalid NIP-05 internet identifier: ${
|
||||
metadata.nip05
|
||||
} pubkey does not match, ${JSON.stringify(wellKnown)}`
|
||||
)
|
||||
}
|
||||
const relays = wellKnown.relays?.[pubkey]
|
||||
if (
|
||||
relays !== undefined &&
|
||||
(!(relays instanceof Array) ||
|
||||
relays.some((relay) => typeof relay !== "string"))
|
||||
) {
|
||||
throw new NostrError(`invalid NIP-05 relays: ${wellKnown}`)
|
||||
}
|
||||
return {
|
||||
name,
|
||||
relays,
|
||||
}
|
||||
}
|
||||
|
||||
export interface InternetIdentifier {
|
||||
name: string
|
||||
relays?: string[]
|
||||
}
|
||||
|
||||
export interface VerificationOptions {
|
||||
https?: boolean
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { EventKind, RawEvent, Unsigned } from "."
|
||||
import { EventKind, RawEvent, signEvent } from "."
|
||||
import { HexOrBechPrivateKey } from "../crypto"
|
||||
|
||||
/**
|
||||
* A text note event. Used for transmitting user posts.
|
||||
@ -9,10 +10,16 @@ export interface TextNote extends RawEvent {
|
||||
kind: EventKind.TextNote
|
||||
}
|
||||
|
||||
export function createTextNote(content: string): Unsigned<TextNote> {
|
||||
return {
|
||||
kind: EventKind.TextNote,
|
||||
tags: [],
|
||||
content,
|
||||
}
|
||||
export function createTextNote(
|
||||
content: string,
|
||||
priv?: HexOrBechPrivateKey
|
||||
): Promise<TextNote> {
|
||||
return signEvent(
|
||||
{
|
||||
kind: EventKind.TextNote,
|
||||
tags: [],
|
||||
content,
|
||||
},
|
||||
priv
|
||||
)
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import assert from "assert"
|
||||
import { EventKind, signEvent } from "../src/event"
|
||||
import { EventKind } from "../src/event"
|
||||
import { createContactList } from "../src/event/contact-list"
|
||||
import { setup } from "./setup"
|
||||
|
||||
@ -47,9 +47,7 @@ describe("contact-list", () => {
|
||||
// After the subscription event sync is done, publish the test event.
|
||||
subscriber.on("eose", async () => {
|
||||
// TODO No signEvent, have a convenient way to do this
|
||||
publisher.publish(
|
||||
await signEvent(createContactList(contacts), subscriberSecret)
|
||||
)
|
||||
publisher.publish(await createContactList(contacts, subscriberSecret))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { EventKind, signEvent } from "../src/event"
|
||||
import { EventKind } from "../src/event"
|
||||
import { parsePublicKey } from "../src/crypto"
|
||||
import assert from "assert"
|
||||
import { setup } from "./setup"
|
||||
@ -49,13 +49,11 @@ describe("dm", () => {
|
||||
const subscriptionId = subscriber.subscribe([])
|
||||
|
||||
subscriber.on("eose", async () => {
|
||||
// TODO No signEvent, do something more convenient
|
||||
const event = await signEvent(
|
||||
await createDirectMessage({
|
||||
const event = await createDirectMessage(
|
||||
{
|
||||
message,
|
||||
recipient: subscriberPubkey,
|
||||
priv: publisherSecret,
|
||||
}),
|
||||
},
|
||||
publisherSecret
|
||||
)
|
||||
publisher.publish(event)
|
||||
|
75
packages/nostr/test/internet-identifier.ts
Normal file
75
packages/nostr/test/internet-identifier.ts
Normal file
@ -0,0 +1,75 @@
|
||||
import assert from "assert"
|
||||
import { defined } from "../src/common"
|
||||
import { EventKind } from "../src/event"
|
||||
import { createSetMetadata } from "../src/event/set-metadata"
|
||||
import { setup } from "./setup"
|
||||
|
||||
describe("internet-identifier", () => {
|
||||
it("present", (done) => {
|
||||
setup(done, ({ publisher, publisherSecret, subscriber, done }) => {
|
||||
subscriber.on("event", async ({ event }) => {
|
||||
// Assert that the internet identifier can be verified.
|
||||
assert.strictEqual(event.kind, EventKind.SetMetadata)
|
||||
if (event.kind === EventKind.SetMetadata) {
|
||||
const identifier = await event.verifyInternetIdentifier({
|
||||
https: false,
|
||||
})
|
||||
assert.ok(identifier)
|
||||
const { name, relays } = defined(identifier)
|
||||
assert.strictEqual(name, "bob")
|
||||
assert.deepStrictEqual(relays, ["ws://example.com"])
|
||||
}
|
||||
done()
|
||||
})
|
||||
|
||||
subscriber.subscribe([])
|
||||
|
||||
// After the subscription event sync is done, publish the test event.
|
||||
subscriber.on("eose", async () => {
|
||||
publisher.publish({
|
||||
...(await createSetMetadata(
|
||||
{
|
||||
about: "",
|
||||
name: "",
|
||||
picture: "",
|
||||
nip05: "bob@localhost:12647",
|
||||
},
|
||||
publisherSecret
|
||||
)),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("missing", (done) => {
|
||||
setup(done, ({ publisher, publisherSecret, subscriber, done }) => {
|
||||
subscriber.on("event", async ({ event }) => {
|
||||
// Assert that undefined is returned if the internet identifier is missing.
|
||||
assert.strictEqual(event.kind, EventKind.SetMetadata)
|
||||
if (event.kind === EventKind.SetMetadata) {
|
||||
const identifier = await event.verifyInternetIdentifier({
|
||||
https: false,
|
||||
})
|
||||
assert.strictEqual(identifier, undefined)
|
||||
}
|
||||
done()
|
||||
})
|
||||
|
||||
subscriber.subscribe([])
|
||||
|
||||
// After the subscription event sync is done, publish the test event.
|
||||
subscriber.on("eose", async () => {
|
||||
publisher.publish({
|
||||
...(await createSetMetadata(
|
||||
{
|
||||
about: "",
|
||||
name: "",
|
||||
picture: "",
|
||||
},
|
||||
publisherSecret
|
||||
)),
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
54
packages/nostr/test/set-metadata.ts
Normal file
54
packages/nostr/test/set-metadata.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { EventKind } from "../src/event"
|
||||
import { parsePublicKey } from "../src/crypto"
|
||||
import assert from "assert"
|
||||
import { setup } from "./setup"
|
||||
import { createSetMetadata } from "../src/event/set-metadata"
|
||||
|
||||
describe("set metadata", () => {
|
||||
const name = "bob"
|
||||
const about = "this is bob"
|
||||
const picture = "https://example.com/bob.jpg"
|
||||
|
||||
// Test that a set metadata event can be published by one client and received by the other.
|
||||
it("publish and receive", (done) => {
|
||||
setup(
|
||||
done,
|
||||
({
|
||||
publisher,
|
||||
publisherSecret,
|
||||
publisherPubkey,
|
||||
subscriber,
|
||||
timestamp,
|
||||
done,
|
||||
}) => {
|
||||
// Expect the test event.
|
||||
subscriber.on("event", ({ event }) => {
|
||||
assert.strictEqual(event.kind, EventKind.SetMetadata)
|
||||
if (event.kind === EventKind.SetMetadata) {
|
||||
const user = event.getUserMetadata()
|
||||
assert.strictEqual(event.pubkey, parsePublicKey(publisherPubkey))
|
||||
assert.strictEqual(event.created_at, timestamp)
|
||||
assert.strictEqual(event.tags.length, 0)
|
||||
assert.strictEqual(user.name, name)
|
||||
assert.strictEqual(user.about, about)
|
||||
assert.strictEqual(user.picture, picture)
|
||||
}
|
||||
done()
|
||||
})
|
||||
|
||||
subscriber.subscribe([])
|
||||
|
||||
// After the subscription event sync is done, publish the test event.
|
||||
subscriber.on("eose", async () => {
|
||||
publisher.publish({
|
||||
...(await createSetMetadata(
|
||||
{ name, about, picture },
|
||||
publisherSecret
|
||||
)),
|
||||
created_at: timestamp,
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
@ -1,8 +1,8 @@
|
||||
import { EventKind, signEvent, Unsigned } from "../src/event"
|
||||
import { EventKind } from "../src/event"
|
||||
import { parsePublicKey } from "../src/crypto"
|
||||
import assert from "assert"
|
||||
import { setup } from "./setup"
|
||||
import { createTextNote, TextNote } from "../src/event/text"
|
||||
import { createTextNote } from "../src/event/text"
|
||||
|
||||
describe("text note", () => {
|
||||
const note = "hello world"
|
||||
@ -40,16 +40,10 @@ describe("text note", () => {
|
||||
assert.strictEqual(nostr, subscriber)
|
||||
assert.strictEqual(id, subscriptionId)
|
||||
|
||||
// TODO No signEvent, have a convenient way to do this
|
||||
publisher.publish(
|
||||
await signEvent(
|
||||
{
|
||||
...createTextNote(note),
|
||||
created_at: timestamp,
|
||||
} as Unsigned<TextNote>,
|
||||
publisherSecret
|
||||
)
|
||||
)
|
||||
publisher.publish({
|
||||
...(await createTextNote(note, publisherSecret)),
|
||||
created_at: timestamp,
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
@ -57,19 +51,16 @@ describe("text note", () => {
|
||||
|
||||
// Test that a client interprets an "OK" message after publishing a text note.
|
||||
it("publish and ok", function (done) {
|
||||
setup(done, ({ publisher, publisherSecret, url, done }) => {
|
||||
// TODO No signEvent, have a convenient way to do this
|
||||
signEvent(createTextNote(note), publisherSecret).then((event) => {
|
||||
publisher.on("ok", (params, nostr) => {
|
||||
assert.strictEqual(nostr, publisher)
|
||||
assert.strictEqual(params.eventId, event.id)
|
||||
assert.strictEqual(params.relay.toString(), url.toString())
|
||||
assert.strictEqual(params.ok, true)
|
||||
done()
|
||||
})
|
||||
|
||||
publisher.publish(event)
|
||||
setup(done, async ({ publisher, publisherSecret, url, done }) => {
|
||||
const event = await createTextNote(note, publisherSecret)
|
||||
publisher.on("ok", (params, nostr) => {
|
||||
assert.strictEqual(nostr, publisher)
|
||||
assert.strictEqual(params.eventId, event.id)
|
||||
assert.strictEqual(params.relay.toString(), url.toString())
|
||||
assert.strictEqual(params.ok, true)
|
||||
done()
|
||||
})
|
||||
publisher.publish(event)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user