Add domain package

This commit is contained in:
Jon Staab
2026-06-18 11:41:21 -07:00
parent 393c95e107
commit eb451d795b
14 changed files with 675 additions and 0 deletions
@@ -0,0 +1,91 @@
import {describe, it, expect} from "vitest"
import {makeSecret, MUTES, FOLLOWS, getPubkeyTagValues} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {Nip01Signer} from "@welshman/signer"
import {MuteList} from "../src/MuteList"
const signer = new Nip01Signer(makeSecret())
const a = "aa".repeat(32)
const b = "bb".repeat(32)
const c = "cc".repeat(32)
describe("MuteList", () => {
it("round-trips public and private mutes through encryption", async () => {
const list = MuteList.make().addPublicly(a).addPrivately(b)
expect(list.pubkeys.sort()).toEqual([a, b].sort())
expect(list.includes(a)).toBe(true)
expect(list.includes(b)).toBe(true)
expect(list.includes(c)).toBe(false)
const event = await list.toEvent(signer)
expect(event.kind).toBe(MUTES)
expect(event.sig).toBeTruthy()
// Public entry is visible in tags; private entry is encrypted in content.
expect(getPubkeyTagValues(event.tags)).toEqual([a])
expect(event.content).not.toBe("")
// Re-parsing with a capable signer recovers the private entries.
const decrypted = await MuteList.parse(event, signer)
expect(decrypted.isDecrypted).toBe(true)
expect(decrypted.pubkeys.sort()).toEqual([a, b].sort())
// Parsing without a signer exposes only the public entries.
const publicOnly = await MuteList.parse(event)
expect(publicOnly.isDecrypted).toBe(false)
expect(publicOnly.pubkeys).toEqual([a])
})
it("removes from both public and private entries", async () => {
const list = MuteList.make().addPublicly(a).addPrivately(b)
list.remove(a)
list.remove(b)
expect(list.pubkeys).toEqual([])
})
it("preserves undecrypted ciphertext on pass-through serialization", async () => {
const event = await MuteList.make().addPrivately(b).toEvent(signer)
const undecrypted = await MuteList.parse(event)
// We never decrypted, so the original ciphertext must survive untouched.
const template = await undecrypted.getTemplate(signer)
expect(template.content).toBe(event.content)
})
it("refuses private mutation when undecrypted", async () => {
const event = await MuteList.make().addPrivately(b).toEvent(signer)
const undecrypted = await MuteList.parse(event)
expect(() => undecrypted.addPrivately(c)).toThrow()
})
it("toRumor encrypts but does not sign", async () => {
const rumor = await MuteList.make().addPrivately(b).toRumor(signer)
expect(rumor.id).toBeTruthy()
expect((rumor as TrustedEvent).sig).toBeUndefined()
expect(rumor.content).not.toBe("")
})
it("serializes to JSON", async () => {
const list = MuteList.make().addPublicly(a).addPrivately(b)
const json = JSON.parse(JSON.stringify(list))
expect(json.kind).toBe(MUTES)
expect(json.publicTags).toEqual([["p", a]])
expect(json.privateTags).toEqual([["p", b]])
})
it("throws on the wrong kind", async () => {
const event = {kind: FOLLOWS, tags: [], content: "", pubkey: a} as TrustedEvent
await expect(MuteList.parse(event)).rejects.toThrow()
})
})
+73
View File
@@ -0,0 +1,73 @@
import {describe, it, expect} from "vitest"
import {makeSecret, PROFILE, NOTE} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {Nip01Signer} from "@welshman/signer"
import {Profile, displayPubkey} from "../src/Profile"
const signer = new Nip01Signer(makeSecret())
const pubkey = "ee".repeat(32)
const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
({
id: "ff".repeat(32),
pubkey,
created_at: 0,
kind: PROFILE,
tags: [],
content: "",
sig: "00".repeat(64),
...overrides,
}) as TrustedEvent
describe("Profile", () => {
it("parses and re-signs profile content", async () => {
const event = makeEvent({
content: JSON.stringify({name: "alice", about: "hi"}),
tags: [["alt", "profile"]],
})
const profile = Profile.parse(event)
expect(profile.values.name).toBe("alice")
expect(profile.hasName()).toBe(true)
expect(profile.display()).toBe("alice")
const signed = await profile.toEvent(signer)
expect(signed.kind).toBe(PROFILE)
expect(JSON.parse(signed.content).name).toBe("alice")
// Source tags are preserved.
expect(signed.tags).toEqual([["alt", "profile"]])
})
it("derives lnurl from a lud16 address", () => {
const profile = Profile.make({lud16: "alice@example.com"})
expect(profile.values.lnurl).toBeTruthy()
})
it("set merges and re-derives values", () => {
const profile = Profile.make({name: "alice"})
profile.set({about: "hello"})
expect(profile.values.name).toBe("alice")
expect(profile.values.about).toBe("hello")
})
it("display falls back to a shortened npub", () => {
const profile = Profile.parse(makeEvent({content: "{}"}))
expect(profile.display()).toBe(displayPubkey(pubkey))
})
it("serializes to JSON", () => {
const profile = Profile.make({name: "alice"})
expect(JSON.parse(JSON.stringify(profile))).toEqual({name: "alice"})
})
it("throws on the wrong kind", () => {
expect(() => Profile.parse(makeEvent({kind: NOTE}))).toThrow()
})
})