Merge pull request #478 from v0l/nostr-package-nip09-event-deletion

`nostr` package: NIP-09 event deletion
This commit is contained in:
sistemd 2023-04-04 22:10:38 +02:00 committed by GitHub
commit 293510069c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 134 additions and 2 deletions

View File

@ -12,5 +12,6 @@ module.exports = {
rules: {
"require-await": "error",
eqeqeq: "error",
"object-shorthand": "warn",
},
}

View File

@ -18,7 +18,7 @@ _Progress: 8/34 (23%)._
- [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-09: Event Deletion
- [x] 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

View File

@ -0,0 +1,48 @@
import { EventId, EventKind, RawEvent, signEvent } from "."
import { NostrError } from "../common"
import { HexOrBechPrivateKey } from "../crypto"
/**
* A deletion event. Used for marking published events as deleted.
*
* Related NIPs: NIP-09.
*/
export interface Deletion extends RawEvent {
kind: EventKind.Deletion
/**
* The IDs of events to delete.
*/
getEvents(): EventId[]
}
/**
* Create a deletion event.
*/
export function createDeletion(
{ events, content }: { events: EventId[]; content?: string },
priv?: HexOrBechPrivateKey
): Promise<Deletion> {
return signEvent(
{
kind: EventKind.Deletion,
tags: events.map((id) => ["e", id]),
content: content ?? "",
getEvents,
},
priv
)
}
export function getEvents(this: Deletion): EventId[] {
return this.tags
.filter((tag) => tag[0] === "e")
.map((tag) => {
if (tag[1] === undefined) {
throw new NostrError(
`invalid deletion event tag: ${JSON.stringify(tag)}`
)
}
return tag[1]
})
}

View File

@ -21,6 +21,7 @@ import {
getRecipient,
} from "./direct-message"
import { ContactList, getContacts } from "./contact-list"
import { Deletion, getEvents } from "./deletion"
// TODO Add remaining event types
@ -69,8 +70,9 @@ export interface Unknown extends RawEvent {
EventKind,
| EventKind.SetMetadata
| EventKind.TextNote
| EventKind.DirectMessage
| EventKind.ContactList
| EventKind.DirectMessage
| EventKind.Deletion
>
}
@ -79,6 +81,7 @@ export type Event =
| TextNote
| ContactList
| DirectMessage
| Deletion
| Unknown
/**
@ -189,6 +192,14 @@ export async function parseEvent(event: RawEvent): Promise<Event> {
}
}
if (event.kind === EventKind.Deletion) {
return {
...event,
kind: EventKind.Deletion,
getEvents,
}
}
return {
...event,
kind: event.kind,

View File

@ -0,0 +1,72 @@
import { EventKind } from "../src/event"
import { parsePublicKey } from "../src/crypto"
import assert from "assert"
import { setup } from "./setup"
import { createTextNote } from "../src/event/text"
import { createDeletion } from "../src/event/deletion"
describe("deletion", () => {
// Test that a deletion event deletes existing events. Test that the deletion event
// is propagated to subscribers.
it("deletes existing events", (done) => {
setup(
done,
({
publisher,
publisherSecret,
publisherPubkey,
subscriber,
timestamp,
done,
}) => {
// The event ID to delete.
let textNoteId: string
// The deletion event ID.
let deletionId: string
// Expect the deletion event (and not the text note event).
subscriber.on("event", ({ event }) => {
assert.strictEqual(event.kind, EventKind.Deletion)
assert.strictEqual(event.id, deletionId)
assert.strictEqual(event.pubkey, parsePublicKey(publisherPubkey))
assert.strictEqual(event.created_at, timestamp)
assert.strictEqual(event.content, "")
if (event.kind === EventKind.Deletion) {
assert.deepStrictEqual(event.getEvents(), [textNoteId])
}
done()
})
createTextNote("hello world", publisherSecret).then((textNote) => {
textNoteId = textNote.id
publisher.publish({
...textNote,
created_at: timestamp,
})
})
publisher.on("ok", async ({ eventId, ok }) => {
assert.strictEqual(ok, true)
if (eventId === textNoteId) {
// After the text note has been published, delete it.
const deletion = await createDeletion(
{ events: [textNoteId] },
publisherSecret
)
deletionId = deletion.id
publisher.publish({
...deletion,
created_at: timestamp,
})
}
if (eventId === deletionId) {
// After the deletion has been published, subscribe to the publisher.
subscriber.subscribe([])
}
})
}
)
})
})