@@ -45,7 +45,7 @@ export class App implements IApp {
|
|||||||
repository: Repository
|
repository: Repository
|
||||||
wrapManager: WrapManager
|
wrapManager: WrapManager
|
||||||
|
|
||||||
private singletons = new Map<Function, unknown>()
|
private singletons = new Map<new (app: IApp) => unknown, unknown>()
|
||||||
private unsubscribers: Unsubscriber[] = []
|
private unsubscribers: Unsubscriber[] = []
|
||||||
|
|
||||||
constructor(options: AppOptions = {}) {
|
constructor(options: AppOptions = {}) {
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ export class BlockedRelayLists extends DerivedPlugin<BlockedRelayList> {
|
|||||||
return this.app.use(Network).loadUsingOutbox(pubkey, {kinds: [BLOCKED_RELAYS]}, relayHints)
|
return this.app.use(Network).loadUsingOutbox(pubkey, {kinds: [BLOCKED_RELAYS]}, relayHints)
|
||||||
}
|
}
|
||||||
|
|
||||||
urls = (pubkey: string): Projection<string[]> =>
|
urls = (pubkey: string): Projection<string[]> => this.project(pubkey, list => list?.urls() ?? [])
|
||||||
this.project(pubkey, list => list?.urls() ?? [])
|
|
||||||
|
|
||||||
update = async (fn: (builder: BlockedRelayListBuilder) => void) => {
|
update = async (fn: (builder: BlockedRelayListBuilder) => void) => {
|
||||||
const user = User.require(this.app)
|
const user = User.require(this.app)
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ export class Logger {
|
|||||||
|
|
||||||
log(
|
log(
|
||||||
source: string,
|
source: string,
|
||||||
{id = randomId(), at = Date.now(), ...message}: {id?: string; at?: number; [key: string]: unknown},
|
{
|
||||||
|
id = randomId(),
|
||||||
|
at = Date.now(),
|
||||||
|
...message
|
||||||
|
}: {id?: string; at?: number; [key: string]: unknown},
|
||||||
) {
|
) {
|
||||||
this.store.update($messages => $messages.concat({source, id, at, ...message}).slice(-1000))
|
this.store.update($messages => $messages.concat({source, id, at, ...message}).slice(-1000))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ export class MessagingRelayLists extends DerivedPlugin<MessagingRelayList> {
|
|||||||
return this.app.use(Network).loadUsingOutbox(pubkey, {kinds: [MESSAGING_RELAYS]}, relayHints)
|
return this.app.use(Network).loadUsingOutbox(pubkey, {kinds: [MESSAGING_RELAYS]}, relayHints)
|
||||||
}
|
}
|
||||||
|
|
||||||
urls = (pubkey: string): Projection<string[]> =>
|
urls = (pubkey: string): Projection<string[]> => this.project(pubkey, list => list?.urls() ?? [])
|
||||||
this.project(pubkey, list => list?.urls() ?? [])
|
|
||||||
|
|
||||||
update = async (fn: (builder: MessagingRelayListBuilder) => void) => {
|
update = async (fn: (builder: MessagingRelayListBuilder) => void) => {
|
||||||
const user = User.require(this.app)
|
const user = User.require(this.app)
|
||||||
|
|||||||
@@ -43,9 +43,8 @@ export class Profiles extends DerivedPlugin<Profile> {
|
|||||||
const read = ($profile: Maybe<Profile>) =>
|
const read = ($profile: Maybe<Profile>) =>
|
||||||
pubkey ? ($profile?.display() ?? displayPubkey(pubkey)) : ""
|
pubkey ? ($profile?.display() ?? displayPubkey(pubkey)) : ""
|
||||||
|
|
||||||
return projection(
|
return projection(pubkey ? derived(this.one(pubkey, ...args), read) : readable(""), () =>
|
||||||
pubkey ? derived(this.one(pubkey, ...args), read) : readable(""),
|
read(pubkey ? this.get(pubkey) : undefined),
|
||||||
() => read(pubkey ? this.get(pubkey) : undefined),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ export class RelayLists extends DerivedPlugin<RelayList> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
urls = (pubkey: string): Projection<string[]> =>
|
urls = (pubkey: string): Projection<string[]> => this.project(pubkey, list => list?.urls() ?? [])
|
||||||
this.project(pubkey, list => list?.urls() ?? [])
|
|
||||||
|
|
||||||
readUrls = (pubkey: string): Projection<string[]> =>
|
readUrls = (pubkey: string): Projection<string[]> =>
|
||||||
this.project(pubkey, list => list?.readUrls() ?? [])
|
this.project(pubkey, list => list?.readUrls() ?? [])
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ export class Rooms {
|
|||||||
this.publish(url, await new RoomLeaveBuilder().setGroup(room.h).toTemplate())
|
this.publish(url, await new RoomLeaveBuilder().setGroup(room.h).toTemplate())
|
||||||
|
|
||||||
addMember = async (url: string, room: RoomMeta, pubkey: string) =>
|
addMember = async (url: string, room: RoomMeta, pubkey: string) =>
|
||||||
this.publish(url, await new RoomAddMemberBuilder().setGroup(room.h).addPubkey(pubkey).toTemplate())
|
this.publish(
|
||||||
|
url,
|
||||||
|
await new RoomAddMemberBuilder().setGroup(room.h).addPubkey(pubkey).toTemplate(),
|
||||||
|
)
|
||||||
|
|
||||||
removeMember = async (url: string, room: RoomMeta, pubkey: string) =>
|
removeMember = async (url: string, room: RoomMeta, pubkey: string) =>
|
||||||
this.publish(
|
this.publish(
|
||||||
|
|||||||
@@ -81,11 +81,7 @@ export class Searches {
|
|||||||
return dec(score) * inc(wotScore / (this.app.use(Wot).max.get() || 1))
|
return dec(score) * inc(wotScore / (this.app.use(Wot).max.get() || 1))
|
||||||
},
|
},
|
||||||
fuseOptions: {
|
fuseOptions: {
|
||||||
keys: [
|
keys: ["nip05", {name: "name", weight: 0.8}, {name: "about", weight: 0.3}],
|
||||||
"nip05",
|
|
||||||
{name: "name", weight: 0.8},
|
|
||||||
{name: "about", weight: 0.3},
|
|
||||||
],
|
|
||||||
threshold: 0.3,
|
threshold: 0.3,
|
||||||
shouldSort: false,
|
shouldSort: false,
|
||||||
// Read fields off the domain reader's parsed `values`; only expose a
|
// Read fields off the domain reader's parsed `values`; only expose a
|
||||||
@@ -96,9 +92,7 @@ export class Searches {
|
|||||||
if (key === "nip05") {
|
if (key === "nip05") {
|
||||||
const nip05 = profile.nip05()
|
const nip05 = profile.nip05()
|
||||||
|
|
||||||
return nip05 && $handlesByNip05.get(nip05)?.pubkey === profile.author()
|
return nip05 && $handlesByNip05.get(nip05)?.pubkey === profile.author() ? nip05 : ""
|
||||||
? nip05
|
|
||||||
: ""
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return profile.values[key] ?? ""
|
return profile.values[key] ?? ""
|
||||||
|
|||||||
@@ -25,8 +25,7 @@ export class SearchRelayLists extends DerivedPlugin<SearchRelayList> {
|
|||||||
return this.app.use(Network).loadUsingOutbox(pubkey, {kinds: [SEARCH_RELAYS]}, relayHints)
|
return this.app.use(Network).loadUsingOutbox(pubkey, {kinds: [SEARCH_RELAYS]}, relayHints)
|
||||||
}
|
}
|
||||||
|
|
||||||
urls = (pubkey: string): Projection<string[]> =>
|
urls = (pubkey: string): Projection<string[]> => this.project(pubkey, list => list?.urls() ?? [])
|
||||||
this.project(pubkey, list => list?.urls() ?? [])
|
|
||||||
|
|
||||||
update = async (fn: (builder: SearchRelayListBuilder) => void) => {
|
update = async (fn: (builder: SearchRelayListBuilder) => void) => {
|
||||||
const user = User.require(this.app)
|
const user = User.require(this.app)
|
||||||
|
|||||||
@@ -45,7 +45,9 @@ export class Sync {
|
|||||||
if (await this.app.use(Relays).hasNegentropy(relay)) {
|
if (await this.app.use(Relays).hasNegentropy(relay)) {
|
||||||
await net.push({filters, events, relays: [relay]})
|
await net.push({filters, events, relays: [relay]})
|
||||||
} else {
|
} else {
|
||||||
await Promise.all(events.map((event: SignedEvent) => net.publish({event, relays: [relay]})))
|
await Promise.all(
|
||||||
|
events.map((event: SignedEvent) => net.publish({event, relays: [relay]})),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type {Subscriber} from "svelte/store"
|
import type {Subscriber} from "svelte/store"
|
||||||
import {writable} from "svelte/store"
|
import {writable} from "svelte/store"
|
||||||
import type {Override} from "@welshman/lib"
|
import type {Override} from "@welshman/lib"
|
||||||
import {append, TaskQueue, ensurePlural, remove, defer, sleep, nth, uniq, without} from "@welshman/lib"
|
import {append, TaskQueue, ensurePlural, remove, defer, sleep, nth, without} from "@welshman/lib"
|
||||||
import {
|
import {
|
||||||
HashedEvent,
|
HashedEvent,
|
||||||
EventTemplate,
|
EventTemplate,
|
||||||
|
|||||||
@@ -160,7 +160,9 @@ export class Wot {
|
|||||||
if (pubkey) {
|
if (pubkey) {
|
||||||
const theirFollows = $follows.get(pubkey)?.pubkeys() ?? []
|
const theirFollows = $follows.get(pubkey)?.pubkeys() ?? []
|
||||||
|
|
||||||
follows = theirFollows.filter(other => ($follows.get(other)?.pubkeys() ?? []).includes(target))
|
follows = theirFollows.filter(other =>
|
||||||
|
($follows.get(other)?.pubkeys() ?? []).includes(target),
|
||||||
|
)
|
||||||
mutes = theirFollows.filter(other => ($mutes.get(other)?.pubkeys() ?? []).includes(target))
|
mutes = theirFollows.filter(other => ($mutes.get(other)?.pubkeys() ?? []).includes(target))
|
||||||
} else {
|
} else {
|
||||||
follows = Array.from($followers.get(target) || [])
|
follows = Array.from($followers.get(target) || [])
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import {get, writable} from "svelte/store"
|
|
||||||
import {TaskQueue, uniq, now} from "@welshman/lib"
|
import {TaskQueue, uniq, now} from "@welshman/lib"
|
||||||
import {getPubkeyTagValues, prep} from "@welshman/util"
|
import {getPubkeyTagValues, prep} from "@welshman/util"
|
||||||
import type {TrustedEvent, SignedEvent, EventTemplate} from "@welshman/util"
|
import type {TrustedEvent, SignedEvent, EventTemplate} from "@welshman/util"
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import type {Profile} from "@welshman/domain"
|
|||||||
import {deriveDeduplicated, deriveDeduplicatedByValue} from "@welshman/store"
|
import {deriveDeduplicated, deriveDeduplicatedByValue} from "@welshman/store"
|
||||||
import {LoadableMapPlugin, projection} from "./base.js"
|
import {LoadableMapPlugin, projection} from "./base.js"
|
||||||
import type {Projection} from "./base.js"
|
import type {Projection} from "./base.js"
|
||||||
import type {IApp} from "../app.js"
|
|
||||||
import {Profiles} from "./profiles.js"
|
import {Profiles} from "./profiles.js"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,11 +146,13 @@ export class Zappers extends LoadableMapPlugin<Zapper> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const stores: Readable<any>[] = [this.index.$, ...splits.map(split => profiles.one(split.pubkey))]
|
const stores: Readable<any>[] = [
|
||||||
|
this.index.$,
|
||||||
|
...splits.map(split => profiles.one(split.pubkey)),
|
||||||
|
]
|
||||||
|
|
||||||
return projection(
|
return projection(deriveDeduplicatedByValue(stores, read), () =>
|
||||||
deriveDeduplicatedByValue(stores, read),
|
read([this.index.get(), ...splits.map(split => profiles.get(split.pubkey))]),
|
||||||
() => read([this.index.get(), ...splits.map(split => profiles.get(split.pubkey))]),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type {Unsubscriber} from "svelte/store"
|
import type {Unsubscriber} from "svelte/store"
|
||||||
import {on, noop, always, call} from "@welshman/lib"
|
import {on, noop, always} from "@welshman/lib"
|
||||||
import {WRAP, isDVMKind, isEphemeralKind, verifyEvent} from "@welshman/util"
|
import {WRAP, isDVMKind, isEphemeralKind, verifyEvent} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {SocketEvent, isRelayEvent, makeSocketPolicyAuth} from "@welshman/net"
|
import {SocketEvent, isRelayEvent, makeSocketPolicyAuth} from "@welshman/net"
|
||||||
@@ -62,11 +62,7 @@ export const appPolicyAuthUnlessBlocked = makeAppPolicyAuth((socket, app) => {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return !app
|
return !app.use(BlockedRelayLists).urls(app.user.pubkey).get().includes(socket.url)
|
||||||
.use(BlockedRelayLists)
|
|
||||||
.urls(app.user.pubkey)
|
|
||||||
.get()
|
|
||||||
.includes(socket.url)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,11 +1,5 @@
|
|||||||
import {describe, it, expect} from "vitest"
|
import {describe, it, expect} from "vitest"
|
||||||
import {
|
import {makeSecret, BOOKMARKS, NOTE, getEventTagValues, getTopicTagValues} from "@welshman/util"
|
||||||
makeSecret,
|
|
||||||
BOOKMARKS,
|
|
||||||
NOTE,
|
|
||||||
getEventTagValues,
|
|
||||||
getTopicTagValues,
|
|
||||||
} from "@welshman/util"
|
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {Nip01Signer} from "@welshman/signer"
|
import {Nip01Signer} from "@welshman/signer"
|
||||||
import {BookmarkList, BookmarkListBuilder} from "../src/kinds/BookmarkList"
|
import {BookmarkList, BookmarkListBuilder} from "../src/kinds/BookmarkList"
|
||||||
@@ -83,7 +77,12 @@ describe("BookmarkList", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("removeBookmark removes by value", async () => {
|
it("removeBookmark removes by value", async () => {
|
||||||
const event = makeEvent({tags: [["e", noteId], ["e", noteId2]]})
|
const event = makeEvent({
|
||||||
|
tags: [
|
||||||
|
["e", noteId],
|
||||||
|
["e", noteId2],
|
||||||
|
],
|
||||||
|
})
|
||||||
const list = await BookmarkList.fromEvent(event)
|
const list = await BookmarkList.fromEvent(event)
|
||||||
|
|
||||||
const tmpl = await list.builder().removeBookmark(noteId).toTemplate(signer)
|
const tmpl = await list.builder().removeBookmark(noteId).toTemplate(signer)
|
||||||
|
|||||||
@@ -49,7 +49,14 @@ describe("Classified", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("defaults the price currency to SAT", async () => {
|
it("defaults the price currency to SAT", async () => {
|
||||||
const c = await Classified.fromEvent(makeEvent({tags: [["d", "x"], ["price", "50"]]}))
|
const c = await Classified.fromEvent(
|
||||||
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["d", "x"],
|
||||||
|
["price", "50"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
expect(c.price()).toEqual({amount: 50, currency: "SAT", frequency: ""})
|
expect(c.price()).toEqual({amount: 50, currency: "SAT", frequency: ""})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ const makeEvent = (o: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
describe("FeedList", () => {
|
describe("FeedList", () => {
|
||||||
it("reads saved feed addresses", async () => {
|
it("reads saved feed addresses", async () => {
|
||||||
const reader = await FeedList.fromEvent(
|
const reader = await FeedList.fromEvent(
|
||||||
makeEvent({tags: [["a", addressA], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["a", addressA],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(reader.addresses()).toEqual([addressA])
|
expect(reader.addresses()).toEqual([addressA])
|
||||||
@@ -35,7 +40,12 @@ describe("FeedList", () => {
|
|||||||
|
|
||||||
it("round-trips without duplicating represented tags", async () => {
|
it("round-trips without duplicating represented tags", async () => {
|
||||||
const reader = await FeedList.fromEvent(
|
const reader = await FeedList.fromEvent(
|
||||||
makeEvent({tags: [["a", addressA], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["a", addressA],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await reader.builder().toTemplate(signer)
|
const tmpl = await reader.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -69,7 +69,12 @@ describe("FollowList", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("removeFollow removes by value", async () => {
|
it("removeFollow removes by value", async () => {
|
||||||
const event = makeEvent({tags: [["p", a], ["p", b]]})
|
const event = makeEvent({
|
||||||
|
tags: [
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
],
|
||||||
|
})
|
||||||
const list = await FollowList.fromEvent(event)
|
const list = await FollowList.fromEvent(event)
|
||||||
|
|
||||||
const tmpl = await list.builder().removeFollow(a).toTemplate(signer)
|
const tmpl = await list.builder().removeFollow(a).toTemplate(signer)
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ const pubkey = "ee".repeat(32)
|
|||||||
|
|
||||||
const g1 = `34550:${"aa".repeat(32)}:dev`
|
const g1 = `34550:${"aa".repeat(32)}:dev`
|
||||||
const g2 = `34550:${"bb".repeat(32)}:art`
|
const g2 = `34550:${"bb".repeat(32)}:art`
|
||||||
const g3 = `34550:${"cc".repeat(32)}:music`
|
|
||||||
|
|
||||||
const makeEvent = (o: Partial<TrustedEvent> = {}): TrustedEvent =>
|
const makeEvent = (o: Partial<TrustedEvent> = {}): TrustedEvent =>
|
||||||
({
|
({
|
||||||
@@ -67,7 +66,12 @@ describe("GroupList", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("removeGroup removes by address", async () => {
|
it("removeGroup removes by address", async () => {
|
||||||
const event = makeEvent({tags: [["a", g1], ["a", g2]]})
|
const event = makeEvent({
|
||||||
|
tags: [
|
||||||
|
["a", g1],
|
||||||
|
["a", g2],
|
||||||
|
],
|
||||||
|
})
|
||||||
const list = await GroupList.fromEvent(event)
|
const list = await GroupList.fromEvent(event)
|
||||||
|
|
||||||
const tmpl = await list.builder().removeGroup(g1).toTemplate(signer)
|
const tmpl = await list.builder().removeGroup(g1).toTemplate(signer)
|
||||||
|
|||||||
@@ -46,7 +46,12 @@ describe("HandlerRecommendation", () => {
|
|||||||
|
|
||||||
it("falls back to the first recommendation without a web marker", async () => {
|
it("falls back to the first recommendation without a web marker", async () => {
|
||||||
const rec = await HandlerRecommendation.fromEvent(
|
const rec = await HandlerRecommendation.fromEvent(
|
||||||
makeEvent({tags: [["d", "1"], ["a", otherAddress, "", "android"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["d", "1"],
|
||||||
|
["a", otherAddress, "", "android"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(rec.handlerAddress()).toBe(otherAddress)
|
expect(rec.handlerAddress()).toBe(otherAddress)
|
||||||
|
|||||||
@@ -25,7 +25,13 @@ const makeEvent = (o: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
describe("PinList", () => {
|
describe("PinList", () => {
|
||||||
it("reads pinned event ids and addresses", async () => {
|
it("reads pinned event ids and addresses", async () => {
|
||||||
const reader = await PinList.fromEvent(
|
const reader = await PinList.fromEvent(
|
||||||
makeEvent({tags: [["e", eventId], ["a", address], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["e", eventId],
|
||||||
|
["a", address],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(reader.ids()).toEqual([eventId])
|
expect(reader.ids()).toEqual([eventId])
|
||||||
@@ -34,7 +40,13 @@ describe("PinList", () => {
|
|||||||
|
|
||||||
it("round-trips without duplicating represented tags", async () => {
|
it("round-trips without duplicating represented tags", async () => {
|
||||||
const reader = await PinList.fromEvent(
|
const reader = await PinList.fromEvent(
|
||||||
makeEvent({tags: [["e", eventId], ["a", address], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["e", eventId],
|
||||||
|
["a", address],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await reader.builder().toTemplate(signer)
|
const tmpl = await reader.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -23,13 +23,29 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
|
|
||||||
describe("RelayAddMember", () => {
|
describe("RelayAddMember", () => {
|
||||||
it("reads affected pubkeys, deduped", async () => {
|
it("reads affected pubkeys, deduped", async () => {
|
||||||
const op = await RelayAddMember.fromEvent(makeEvent({tags: [["p", a], ["p", b], ["p", a]]}))
|
const op = await RelayAddMember.fromEvent(
|
||||||
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
["p", a],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
expect(op.pubkeys().sort()).toEqual([a, b].sort())
|
expect(op.pubkeys().sort()).toEqual([a, b].sort())
|
||||||
})
|
})
|
||||||
|
|
||||||
it("round-trips with no duplicate p tags and passthrough", async () => {
|
it("round-trips with no duplicate p tags and passthrough", async () => {
|
||||||
const op = await RelayAddMember.fromEvent(makeEvent({tags: [["p", a], ["p", b], ["alt", "x"]]}))
|
const op = await RelayAddMember.fromEvent(
|
||||||
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
const tmpl = await op.builder().toTemplate(signer)
|
const tmpl = await op.builder().toTemplate(signer)
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,13 @@ describe("RelayJoin", () => {
|
|||||||
|
|
||||||
it("round-trips with no duplicate tags and preserves passthrough/content", async () => {
|
it("round-trips with no duplicate tags and preserves passthrough/content", async () => {
|
||||||
const join = await RelayJoin.fromEvent(
|
const join = await RelayJoin.fromEvent(
|
||||||
makeEvent({tags: [["claim", "abc123"], ["alt", "x"]], content: "let me in"}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["claim", "abc123"],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
content: "let me in",
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await join.builder().toTemplate(signer)
|
const tmpl = await join.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -22,7 +22,14 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
|
|
||||||
describe("RelayLeave", () => {
|
describe("RelayLeave", () => {
|
||||||
it("round-trips the group behavior tag without duplication", async () => {
|
it("round-trips the group behavior tag without duplication", async () => {
|
||||||
const leave = await RelayLeave.fromEvent(makeEvent({tags: [["h", group], ["alt", "x"]]}))
|
const leave = await RelayLeave.fromEvent(
|
||||||
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["h", group],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
expect(leave.group()).toBe(group)
|
expect(leave.group()).toBe(group)
|
||||||
|
|
||||||
|
|||||||
@@ -36,21 +36,19 @@ describe("RelayList", () => {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(reader.urls().sort()).toEqual(
|
expect(reader.urls().sort()).toEqual([both, read, write].map(normalizeRelayUrl).sort())
|
||||||
[both, read, write].map(normalizeRelayUrl).sort(),
|
expect(reader.readUrls().sort()).toEqual([both, read].map(normalizeRelayUrl).sort())
|
||||||
)
|
expect(reader.writeUrls().sort()).toEqual([both, write].map(normalizeRelayUrl).sort())
|
||||||
expect(reader.readUrls().sort()).toEqual(
|
|
||||||
[both, read].map(normalizeRelayUrl).sort(),
|
|
||||||
)
|
|
||||||
expect(reader.writeUrls().sort()).toEqual(
|
|
||||||
[both, write].map(normalizeRelayUrl).sort(),
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("round-trips without duplicating represented tags", async () => {
|
it("round-trips without duplicating represented tags", async () => {
|
||||||
const reader = await RelayList.fromEvent(
|
const reader = await RelayList.fromEvent(
|
||||||
makeEvent({
|
makeEvent({
|
||||||
tags: [["r", both], ["r", read, RelayMode.Read], ["alt", "x"]],
|
tags: [
|
||||||
|
["r", both],
|
||||||
|
["r", read, RelayMode.Read],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,13 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
describe("RelayMembers", () => {
|
describe("RelayMembers", () => {
|
||||||
it("reads members from member tags", async () => {
|
it("reads members from member tags", async () => {
|
||||||
const members = await RelayMembers.fromEvent(
|
const members = await RelayMembers.fromEvent(
|
||||||
makeEvent({tags: [["member", a], ["member", b], ["member", a]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["member", a],
|
||||||
|
["member", b],
|
||||||
|
["member", a],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(members.pubkeys().sort()).toEqual([a, b].sort())
|
expect(members.pubkeys().sort()).toEqual([a, b].sort())
|
||||||
@@ -35,7 +41,13 @@ describe("RelayMembers", () => {
|
|||||||
|
|
||||||
it("round-trips with deduped member tags and passthrough", async () => {
|
it("round-trips with deduped member tags and passthrough", async () => {
|
||||||
const members = await RelayMembers.fromEvent(
|
const members = await RelayMembers.fromEvent(
|
||||||
makeEvent({tags: [["member", a], ["member", b], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["member", a],
|
||||||
|
["member", b],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await members.builder().toTemplate(signer)
|
const tmpl = await members.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -89,7 +89,13 @@ describe("RelaySet", () => {
|
|||||||
|
|
||||||
it("setRelays replaces relays but preserves metadata", async () => {
|
it("setRelays replaces relays but preserves metadata", async () => {
|
||||||
const reader = await RelaySet.fromEvent(
|
const reader = await RelaySet.fromEvent(
|
||||||
makeEvent({tags: [["d", "my-set"], ["title", "My Set"], ["relay", relayA]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["d", "my-set"],
|
||||||
|
["title", "My Set"],
|
||||||
|
["relay", relayA],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await reader.builder().setUrls([relayB]).toTemplate(signer)
|
const tmpl = await reader.builder().setUrls([relayB]).toTemplate(signer)
|
||||||
|
|||||||
@@ -24,7 +24,14 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
describe("RoomAddMember", () => {
|
describe("RoomAddMember", () => {
|
||||||
it("reads pubkeys and group", async () => {
|
it("reads pubkeys and group", async () => {
|
||||||
const op = await RoomAddMember.fromEvent(
|
const op = await RoomAddMember.fromEvent(
|
||||||
makeEvent({tags: [["h", "room1"], ["p", a], ["p", b], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["h", "room1"],
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(op.kind).toBe(ROOM_ADD_MEMBER)
|
expect(op.kind).toBe(ROOM_ADD_MEMBER)
|
||||||
@@ -34,7 +41,14 @@ describe("RoomAddMember", () => {
|
|||||||
|
|
||||||
it("round-trips with no duplicated tags", async () => {
|
it("round-trips with no duplicated tags", async () => {
|
||||||
const op = await RoomAddMember.fromEvent(
|
const op = await RoomAddMember.fromEvent(
|
||||||
makeEvent({tags: [["h", "room1"], ["p", a], ["p", b], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["h", "room1"],
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await op.builder().toTemplate(signer)
|
const tmpl = await op.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -24,7 +24,14 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
describe("RoomAdmins", () => {
|
describe("RoomAdmins", () => {
|
||||||
it("reads represented tags", async () => {
|
it("reads represented tags", async () => {
|
||||||
const room = await RoomAdmins.fromEvent(
|
const room = await RoomAdmins.fromEvent(
|
||||||
makeEvent({tags: [["d", "room1"], ["p", a], ["p", b], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["d", "room1"],
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(room.identifier()).toBe("room1")
|
expect(room.identifier()).toBe("room1")
|
||||||
@@ -33,7 +40,14 @@ describe("RoomAdmins", () => {
|
|||||||
|
|
||||||
it("round-trips with no duplicated tags", async () => {
|
it("round-trips with no duplicated tags", async () => {
|
||||||
const room = await RoomAdmins.fromEvent(
|
const room = await RoomAdmins.fromEvent(
|
||||||
makeEvent({tags: [["d", "room1"], ["p", a], ["p", b], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["d", "room1"],
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await room.builder().toTemplate(signer)
|
const tmpl = await room.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -22,7 +22,14 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
|
|
||||||
describe("RoomCreate", () => {
|
describe("RoomCreate", () => {
|
||||||
it("round-trips the group behavior tag without duplication", async () => {
|
it("round-trips the group behavior tag without duplication", async () => {
|
||||||
const create = await RoomCreate.fromEvent(makeEvent({tags: [["h", group], ["alt", "x"]]}))
|
const create = await RoomCreate.fromEvent(
|
||||||
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["h", group],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
expect(create.group()).toBe(group)
|
expect(create.group()).toBe(group)
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,7 @@ import {describe, it, expect} from "vitest"
|
|||||||
import {makeSecret, ROOM_CREATE_PERMISSION, NOTE} from "@welshman/util"
|
import {makeSecret, ROOM_CREATE_PERMISSION, NOTE} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import {Nip01Signer} from "@welshman/signer"
|
import {Nip01Signer} from "@welshman/signer"
|
||||||
import {
|
import {RoomCreatePermission, RoomCreatePermissionBuilder} from "../src/kinds/RoomCreatePermission"
|
||||||
RoomCreatePermission,
|
|
||||||
RoomCreatePermissionBuilder,
|
|
||||||
} from "../src/kinds/RoomCreatePermission"
|
|
||||||
|
|
||||||
const signer = new Nip01Signer(makeSecret())
|
const signer = new Nip01Signer(makeSecret())
|
||||||
const pubkey = "ee".repeat(32)
|
const pubkey = "ee".repeat(32)
|
||||||
@@ -28,7 +25,13 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
describe("RoomCreatePermission", () => {
|
describe("RoomCreatePermission", () => {
|
||||||
it("reads permitted pubkeys from p tags", async () => {
|
it("reads permitted pubkeys from p tags", async () => {
|
||||||
const perm = await RoomCreatePermission.fromEvent(
|
const perm = await RoomCreatePermission.fromEvent(
|
||||||
makeEvent({tags: [["p", a], ["p", b], ["p", a]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
["p", a],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(perm.pubkeys().sort()).toEqual([a, b].sort())
|
expect(perm.pubkeys().sort()).toEqual([a, b].sort())
|
||||||
@@ -38,7 +41,13 @@ describe("RoomCreatePermission", () => {
|
|||||||
|
|
||||||
it("round-trips with no duplicate p tags and passthrough", async () => {
|
it("round-trips with no duplicate p tags and passthrough", async () => {
|
||||||
const perm = await RoomCreatePermission.fromEvent(
|
const perm = await RoomCreatePermission.fromEvent(
|
||||||
makeEvent({tags: [["p", a], ["p", b], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await perm.builder().toTemplate(signer)
|
const tmpl = await perm.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -28,7 +28,14 @@ describe("RoomDelete", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("round-trips the group behavior tag without duplication", async () => {
|
it("round-trips the group behavior tag without duplication", async () => {
|
||||||
const del = await RoomDelete.fromEvent(makeEvent({tags: [["h", group], ["alt", "x"]]}))
|
const del = await RoomDelete.fromEvent(
|
||||||
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["h", group],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
const tmpl = await del.builder().toTemplate(signer)
|
const tmpl = await del.builder().toTemplate(signer)
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ describe("RoomJoin", () => {
|
|||||||
it("reads represented fields", async () => {
|
it("reads represented fields", async () => {
|
||||||
const join = await RoomJoin.fromEvent(
|
const join = await RoomJoin.fromEvent(
|
||||||
makeEvent({
|
makeEvent({
|
||||||
tags: [["h", "room1"], ["claim", "invite-code"], ["alt", "x"]],
|
tags: [
|
||||||
|
["h", "room1"],
|
||||||
|
["claim", "invite-code"],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
content: "please let me in",
|
content: "please let me in",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@@ -36,7 +40,11 @@ describe("RoomJoin", () => {
|
|||||||
it("round-trips with no duplicated tags", async () => {
|
it("round-trips with no duplicated tags", async () => {
|
||||||
const join = await RoomJoin.fromEvent(
|
const join = await RoomJoin.fromEvent(
|
||||||
makeEvent({
|
makeEvent({
|
||||||
tags: [["h", "room1"], ["claim", "invite-code"], ["alt", "x"]],
|
tags: [
|
||||||
|
["h", "room1"],
|
||||||
|
["claim", "invite-code"],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
content: "please let me in",
|
content: "please let me in",
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -28,7 +28,14 @@ describe("RoomLeave", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("round-trips the group behavior tag without duplication", async () => {
|
it("round-trips the group behavior tag without duplication", async () => {
|
||||||
const leave = await RoomLeave.fromEvent(makeEvent({tags: [["h", group], ["alt", "x"]]}))
|
const leave = await RoomLeave.fromEvent(
|
||||||
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["h", group],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
const tmpl = await leave.builder().toTemplate(signer)
|
const tmpl = await leave.builder().toTemplate(signer)
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,12 @@ const makeEvent = (o: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
describe("RoomList", () => {
|
describe("RoomList", () => {
|
||||||
it("reads joined groups", async () => {
|
it("reads joined groups", async () => {
|
||||||
const reader = await RoomList.fromEvent(
|
const reader = await RoomList.fromEvent(
|
||||||
makeEvent({tags: [["group", groupA, relay], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["group", groupA, relay],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(reader.groups()).toEqual([groupA])
|
expect(reader.groups()).toEqual([groupA])
|
||||||
@@ -35,7 +40,12 @@ describe("RoomList", () => {
|
|||||||
|
|
||||||
it("round-trips without duplicating represented tags", async () => {
|
it("round-trips without duplicating represented tags", async () => {
|
||||||
const reader = await RoomList.fromEvent(
|
const reader = await RoomList.fromEvent(
|
||||||
makeEvent({tags: [["group", groupA, relay], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["group", groupA, relay],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await reader.builder().toTemplate(signer)
|
const tmpl = await reader.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -25,7 +25,14 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
describe("RoomMembers", () => {
|
describe("RoomMembers", () => {
|
||||||
it("reads represented tags", async () => {
|
it("reads represented tags", async () => {
|
||||||
const room = await RoomMembers.fromEvent(
|
const room = await RoomMembers.fromEvent(
|
||||||
makeEvent({tags: [["d", "room1"], ["p", a], ["p", b], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["d", "room1"],
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(room.identifier()).toBe("room1")
|
expect(room.identifier()).toBe("room1")
|
||||||
@@ -36,7 +43,14 @@ describe("RoomMembers", () => {
|
|||||||
|
|
||||||
it("round-trips with no duplicated tags", async () => {
|
it("round-trips with no duplicated tags", async () => {
|
||||||
const room = await RoomMembers.fromEvent(
|
const room = await RoomMembers.fromEvent(
|
||||||
makeEvent({tags: [["d", "room1"], ["p", a], ["p", b], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["d", "room1"],
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await room.builder().toTemplate(signer)
|
const tmpl = await room.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -23,7 +23,14 @@ const makeEvent = (overrides: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
|
|
||||||
describe("RoomRemoveMember", () => {
|
describe("RoomRemoveMember", () => {
|
||||||
it("uses the remove kind and reads pubkeys", async () => {
|
it("uses the remove kind and reads pubkeys", async () => {
|
||||||
const op = await RoomRemoveMember.fromEvent(makeEvent({tags: [["h", "room1"], ["p", a]]}))
|
const op = await RoomRemoveMember.fromEvent(
|
||||||
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["h", "room1"],
|
||||||
|
["p", a],
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
expect(op.kind).toBe(ROOM_REMOVE_MEMBER)
|
expect(op.kind).toBe(ROOM_REMOVE_MEMBER)
|
||||||
expect(op.pubkeys()).toEqual([a])
|
expect(op.pubkeys()).toEqual([a])
|
||||||
@@ -31,7 +38,13 @@ describe("RoomRemoveMember", () => {
|
|||||||
|
|
||||||
it("round-trips through the remove builder", async () => {
|
it("round-trips through the remove builder", async () => {
|
||||||
const op = await RoomRemoveMember.fromEvent(
|
const op = await RoomRemoveMember.fromEvent(
|
||||||
makeEvent({tags: [["h", "room1"], ["p", a], ["p", b]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["h", "room1"],
|
||||||
|
["p", a],
|
||||||
|
["p", b],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await op.builder().toTemplate(signer)
|
const tmpl = await op.builder().toTemplate(signer)
|
||||||
@@ -43,7 +56,10 @@ describe("RoomRemoveMember", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("builds from a fresh remove builder", async () => {
|
it("builds from a fresh remove builder", async () => {
|
||||||
const tmpl = await new RoomRemoveMemberBuilder().setGroup("room2").addPubkey(a).toTemplate(signer)
|
const tmpl = await new RoomRemoveMemberBuilder()
|
||||||
|
.setGroup("room2")
|
||||||
|
.addPubkey(a)
|
||||||
|
.toTemplate(signer)
|
||||||
|
|
||||||
expect(tmpl.kind).toBe(ROOM_REMOVE_MEMBER)
|
expect(tmpl.kind).toBe(ROOM_REMOVE_MEMBER)
|
||||||
expect(getTagValue("h", tmpl.tags)).toBe("room2")
|
expect(getTagValue("h", tmpl.tags)).toBe("room2")
|
||||||
|
|||||||
@@ -25,7 +25,12 @@ const makeEvent = (o: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
describe("SearchRelayList", () => {
|
describe("SearchRelayList", () => {
|
||||||
it("reads search relay urls", async () => {
|
it("reads search relay urls", async () => {
|
||||||
const reader = await SearchRelayList.fromEvent(
|
const reader = await SearchRelayList.fromEvent(
|
||||||
makeEvent({tags: [["relay", relayA], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["relay", relayA],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(reader.urls()).toEqual([normalizeRelayUrl(relayA)])
|
expect(reader.urls()).toEqual([normalizeRelayUrl(relayA)])
|
||||||
@@ -35,7 +40,12 @@ describe("SearchRelayList", () => {
|
|||||||
|
|
||||||
it("round-trips without duplicating represented tags", async () => {
|
it("round-trips without duplicating represented tags", async () => {
|
||||||
const reader = await SearchRelayList.fromEvent(
|
const reader = await SearchRelayList.fromEvent(
|
||||||
makeEvent({tags: [["relay", relayA], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["relay", relayA],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await reader.builder().toTemplate(signer)
|
const tmpl = await reader.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -26,7 +26,13 @@ const makeEvent = (o: Partial<TrustedEvent> = {}): TrustedEvent =>
|
|||||||
describe("TopicList", () => {
|
describe("TopicList", () => {
|
||||||
it("reads followed topics and interest-set addresses", async () => {
|
it("reads followed topics and interest-set addresses", async () => {
|
||||||
const reader = await TopicList.fromEvent(
|
const reader = await TopicList.fromEvent(
|
||||||
makeEvent({tags: [["t", topicA], ["a", address], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["t", topicA],
|
||||||
|
["a", address],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
expect(reader.topics()).toEqual([topicA])
|
expect(reader.topics()).toEqual([topicA])
|
||||||
@@ -37,7 +43,13 @@ describe("TopicList", () => {
|
|||||||
|
|
||||||
it("round-trips without duplicating represented tags", async () => {
|
it("round-trips without duplicating represented tags", async () => {
|
||||||
const reader = await TopicList.fromEvent(
|
const reader = await TopicList.fromEvent(
|
||||||
makeEvent({tags: [["t", topicA], ["a", address], ["alt", "x"]]}),
|
makeEvent({
|
||||||
|
tags: [
|
||||||
|
["t", topicA],
|
||||||
|
["a", address],
|
||||||
|
["alt", "x"],
|
||||||
|
],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const tmpl = await reader.builder().toTemplate(signer)
|
const tmpl = await reader.builder().toTemplate(signer)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {first, partition, randomId, spec} from "@welshman/lib"
|
import {first, partition, randomId, spec} from "@welshman/lib"
|
||||||
import type {Maybe, MaybeAsync} from "@welshman/lib"
|
import type {MaybeAsync} from "@welshman/lib"
|
||||||
import {stamp, prep, isParameterizedReplaceableKind} from "@welshman/util"
|
import {stamp, prep, isParameterizedReplaceableKind} from "@welshman/util"
|
||||||
import type {EventTemplate, SignedEvent, HashedEvent} from "@welshman/util"
|
import type {EventTemplate, SignedEvent, HashedEvent} from "@welshman/util"
|
||||||
import type {ISigner} from "@welshman/signer"
|
import type {ISigner} from "@welshman/signer"
|
||||||
|
|||||||
@@ -34,9 +34,9 @@ export abstract class EventReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static factory<T extends EventReader>(this: new (event: TrustedEvent) => T, signer?: ISigner) {
|
static factory<T extends EventReader>(this: new (event: TrustedEvent) => T, signer?: ISigner) {
|
||||||
const Reader = this
|
// `this` (the subclass constructor) is captured lexically by the arrow, so
|
||||||
|
// the returned factory stays bound to the right kind.
|
||||||
return (event: TrustedEvent) => EventReader.fromEventUsingSubclass(Reader, event, signer)
|
return (event: TrustedEvent) => EventReader.fromEventUsingSubclass(this, event, signer)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async parse(signer?: ISigner): Promise<void> {}
|
protected async parse(signer?: ISigner): Promise<void> {}
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ import type {ISigner} from "@welshman/signer"
|
|||||||
import {EventBuilder} from "./EventBuilder.js"
|
import {EventBuilder} from "./EventBuilder.js"
|
||||||
import type {ListReader} from "./ListReader.js"
|
import type {ListReader} from "./ListReader.js"
|
||||||
|
|
||||||
export abstract class ListBuilder<Reader extends ListReader = ListReader> extends EventBuilder<Reader> {
|
export abstract class ListBuilder<
|
||||||
|
Reader extends ListReader = ListReader,
|
||||||
|
> extends EventBuilder<Reader> {
|
||||||
publicTags: string[][] = []
|
publicTags: string[][] = []
|
||||||
privateTags: string[][] = []
|
privateTags: string[][] = []
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import {parseJson} from "@welshman/lib"
|
import {parseJson} from "@welshman/lib"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
|
||||||
import {decrypt} from "@welshman/signer"
|
import {decrypt} from "@welshman/signer"
|
||||||
import type {ISigner} from "@welshman/signer"
|
import type {ISigner} from "@welshman/signer"
|
||||||
import {EventReader} from "./EventReader.js"
|
import {EventReader} from "./EventReader.js"
|
||||||
@@ -24,7 +23,9 @@ export abstract class ListReader extends EventReader {
|
|||||||
const json = parseJson(plaintext)
|
const json = parseJson(plaintext)
|
||||||
|
|
||||||
if (Array.isArray(json)) {
|
if (Array.isArray(json)) {
|
||||||
this.privateTags = json.filter(tag => Array.isArray(tag) && tag.length > 0 && tag.every(v => typeof v === "string"))
|
this.privateTags = json.filter(
|
||||||
|
tag => Array.isArray(tag) && tag.length > 0 && tag.every(v => typeof v === "string"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// pass
|
// pass
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import {first} from "@welshman/lib"
|
import {first} from "@welshman/lib"
|
||||||
import {CLASSIFIED, getTag, getTagValue, getTagValues, getTopicTagValues} from "@welshman/util"
|
import {CLASSIFIED, getTag, getTagValue, getTagValues, getTopicTagValues} from "@welshman/util"
|
||||||
import type {ISigner} from "@welshman/signer"
|
|
||||||
import {EventReader} from "../EventReader.js"
|
import {EventReader} from "../EventReader.js"
|
||||||
import {EventBuilder} from "../EventBuilder.js"
|
import {EventBuilder} from "../EventBuilder.js"
|
||||||
|
|
||||||
@@ -10,7 +9,9 @@ export type ClassifiedPrice = {
|
|||||||
frequency: string
|
frequency: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsePrice = ([, amount = "0", currency = "SAT", frequency = ""]: string[]): ClassifiedPrice | undefined => {
|
const parsePrice = ([, amount = "0", currency = "SAT", frequency = ""]: string[]):
|
||||||
|
| ClassifiedPrice
|
||||||
|
| undefined => {
|
||||||
const value = parseFloat(amount)
|
const value = parseFloat(amount)
|
||||||
|
|
||||||
if (!isNaN(value)) {
|
if (!isNaN(value)) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import {first} from "@welshman/lib"
|
import {first} from "@welshman/lib"
|
||||||
import {COMMENT, Address, getTagValue} from "@welshman/util"
|
import {COMMENT, Address, getTagValue} from "@welshman/util"
|
||||||
import type {TrustedEvent} from "@welshman/util"
|
import type {TrustedEvent} from "@welshman/util"
|
||||||
import type {ISigner} from "@welshman/signer"
|
|
||||||
import {EventReader} from "../EventReader.js"
|
import {EventReader} from "../EventReader.js"
|
||||||
import {EventBuilder} from "../EventBuilder.js"
|
import {EventBuilder} from "../EventBuilder.js"
|
||||||
|
|
||||||
@@ -65,7 +64,11 @@ export class CommentBuilder extends EventBuilder<Comment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setRoot(kind: number, id: string, pubkey: string, identifier?: string) {
|
setRoot(kind: number, id: string, pubkey: string, identifier?: string) {
|
||||||
this.rootTags = [["K", String(kind)], ["E", id], ["P", pubkey]]
|
this.rootTags = [
|
||||||
|
["K", String(kind)],
|
||||||
|
["E", id],
|
||||||
|
["P", pubkey],
|
||||||
|
]
|
||||||
|
|
||||||
if (identifier) {
|
if (identifier) {
|
||||||
this.rootTags.push(["A", new Address(kind, pubkey, identifier).toString()])
|
this.rootTags.push(["A", new Address(kind, pubkey, identifier).toString()])
|
||||||
@@ -75,7 +78,11 @@ export class CommentBuilder extends EventBuilder<Comment> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setParent(kind: number, id: string, pubkey: string, identifier?: string) {
|
setParent(kind: number, id: string, pubkey: string, identifier?: string) {
|
||||||
this.parentTags = [["k", String(kind)], ["e", id], ["p", pubkey]]
|
this.parentTags = [
|
||||||
|
["k", String(kind)],
|
||||||
|
["e", id],
|
||||||
|
["p", pubkey],
|
||||||
|
]
|
||||||
|
|
||||||
if (identifier) {
|
if (identifier) {
|
||||||
this.parentTags.push(["a", new Address(kind, pubkey, identifier).toString()])
|
this.parentTags.push(["a", new Address(kind, pubkey, identifier).toString()])
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ export class Poll extends EventReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
options(): PollOption[] {
|
options(): PollOption[] {
|
||||||
return this.event.tags
|
return this.event.tags.filter(t => t[0] === "option").map(([, id, label = id]) => ({id, label}))
|
||||||
.filter(t => t[0] === "option")
|
|
||||||
.map(([, id, label = id]) => ({id, label}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pollType(): PollType {
|
pollType(): PollType {
|
||||||
@@ -148,10 +146,7 @@ export class PollBuilder extends EventBuilder<Poll> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected buildTags() {
|
protected buildTags() {
|
||||||
const tags: string[][] = [
|
const tags: string[][] = [...this.optionTags, this.pollTypeTag ?? ["polltype", "singlechoice"]]
|
||||||
...this.optionTags,
|
|
||||||
this.pollTypeTag ?? ["polltype", "singlechoice"],
|
|
||||||
]
|
|
||||||
|
|
||||||
if (this.endsAtTag) tags.push(this.endsAtTag)
|
if (this.endsAtTag) tags.push(this.endsAtTag)
|
||||||
|
|
||||||
|
|||||||
@@ -1638,13 +1638,14 @@ export const member =
|
|||||||
/** Returns a function that checks whether all predicates pass */
|
/** Returns a function that checks whether all predicates pass */
|
||||||
export const allPass =
|
export const allPass =
|
||||||
<T>(...predicates: ((x: T) => unknown)[]) =>
|
<T>(...predicates: ((x: T) => unknown)[]) =>
|
||||||
(x: T) => predicates.every(predicate => predicate(x))
|
(x: T) =>
|
||||||
|
predicates.every(predicate => predicate(x))
|
||||||
|
|
||||||
/** Returns a function that checks whether some predicate passes */
|
/** Returns a function that checks whether some predicate passes */
|
||||||
export const somePass =
|
export const somePass =
|
||||||
<T>(...predicates: ((x: T) => unknown)[]) =>
|
<T>(...predicates: ((x: T) => unknown)[]) =>
|
||||||
(x: T) => predicates.some(predicate => predicate(x))
|
(x: T) =>
|
||||||
|
predicates.some(predicate => predicate(x))
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
// Sets
|
// Sets
|
||||||
|
|||||||
@@ -367,8 +367,8 @@ describe("policy", () => {
|
|||||||
// Socket closes
|
// Socket closes
|
||||||
socket.emit(SocketEvent.Status, SocketStatus.Closed)
|
socket.emit(SocketEvent.Status, SocketStatus.Closed)
|
||||||
|
|
||||||
// Advance past the reopen delay
|
// Advance past the reopen delay (the ~5s flap guard)
|
||||||
await vi.advanceTimersByTimeAsync(31000)
|
await vi.advanceTimersByTimeAsync(10000)
|
||||||
|
|
||||||
// Should resend the pending event
|
// Should resend the pending event
|
||||||
expect(sendSpy).toHaveBeenCalledWith(event)
|
expect(sendSpy).toHaveBeenCalledWith(event)
|
||||||
@@ -387,8 +387,8 @@ describe("policy", () => {
|
|||||||
// Socket closes
|
// Socket closes
|
||||||
socket.emit(SocketEvent.Status, SocketStatus.Closed)
|
socket.emit(SocketEvent.Status, SocketStatus.Closed)
|
||||||
|
|
||||||
// Advance past the reopen delay
|
// Advance past the reopen delay (the ~5s flap guard)
|
||||||
await vi.advanceTimersByTimeAsync(30000)
|
await vi.advanceTimersByTimeAsync(10000)
|
||||||
|
|
||||||
// Should resend the pending request
|
// Should resend the pending request
|
||||||
expect(sendSpy).toHaveBeenCalledWith(req)
|
expect(sendSpy).toHaveBeenCalledWith(req)
|
||||||
@@ -414,8 +414,8 @@ describe("policy", () => {
|
|||||||
// Should not resend yet to prevent flapping
|
// Should not resend yet to prevent flapping
|
||||||
expect(sendSpy).not.toHaveBeenCalled()
|
expect(sendSpy).not.toHaveBeenCalled()
|
||||||
|
|
||||||
// Advance remaining time
|
// Advance remaining time past the reopen delay
|
||||||
await vi.advanceTimersByTimeAsync(25000)
|
await vi.advanceTimersByTimeAsync(2000)
|
||||||
|
|
||||||
// Now should resend
|
// Now should resend
|
||||||
expect(sendSpy).toHaveBeenCalledWith(event)
|
expect(sendSpy).toHaveBeenCalledWith(event)
|
||||||
|
|||||||
@@ -80,16 +80,28 @@ export class WrappedSigner extends Emitter implements ISigner {
|
|||||||
|
|
||||||
nip04 = {
|
nip04 = {
|
||||||
encrypt: async (pubkey: string, message: string) =>
|
encrypt: async (pubkey: string, message: string) =>
|
||||||
this.wrapMethod("nip04.encrypt", () => this.signer.nip04.encrypt(pubkey, message), [pubkey, message]),
|
this.wrapMethod("nip04.encrypt", () => this.signer.nip04.encrypt(pubkey, message), [
|
||||||
|
pubkey,
|
||||||
|
message,
|
||||||
|
]),
|
||||||
decrypt: async (pubkey: string, message: string) =>
|
decrypt: async (pubkey: string, message: string) =>
|
||||||
this.wrapMethod("nip04.decrypt", () => this.signer.nip04.decrypt(pubkey, message), [pubkey, message]),
|
this.wrapMethod("nip04.decrypt", () => this.signer.nip04.decrypt(pubkey, message), [
|
||||||
|
pubkey,
|
||||||
|
message,
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
nip44 = {
|
nip44 = {
|
||||||
encrypt: async (pubkey: string, message: string) =>
|
encrypt: async (pubkey: string, message: string) =>
|
||||||
this.wrapMethod("nip44.encrypt", () => this.signer.nip44.encrypt(pubkey, message), [pubkey, message]),
|
this.wrapMethod("nip44.encrypt", () => this.signer.nip44.encrypt(pubkey, message), [
|
||||||
|
pubkey,
|
||||||
|
message,
|
||||||
|
]),
|
||||||
decrypt: async (pubkey: string, message: string) =>
|
decrypt: async (pubkey: string, message: string) =>
|
||||||
this.wrapMethod("nip44.decrypt", () => this.signer.nip44.decrypt(pubkey, message), [pubkey, message]),
|
this.wrapMethod("nip44.decrypt", () => this.signer.nip44.decrypt(pubkey, message), [
|
||||||
|
pubkey,
|
||||||
|
message,
|
||||||
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanup() {
|
async cleanup() {
|
||||||
|
|||||||
@@ -9,6 +9,11 @@ export enum ManagementMethod {
|
|||||||
UnallowPubkey = "unallowpubkey",
|
UnallowPubkey = "unallowpubkey",
|
||||||
ListBannedPubkeys = "listbannedpubkeys",
|
ListBannedPubkeys = "listbannedpubkeys",
|
||||||
ListAllowedPubkeys = "listallowedpubkeys",
|
ListAllowedPubkeys = "listallowedpubkeys",
|
||||||
|
CreateRole = "createrole",
|
||||||
|
EditRole = "editrole",
|
||||||
|
DeleteRole = "deleterole",
|
||||||
|
AssignRole = "assignrole",
|
||||||
|
UnassignRole = "unassignrole",
|
||||||
ListEventsNeedingModeration = "listeventsneedingmoderation",
|
ListEventsNeedingModeration = "listeventsneedingmoderation",
|
||||||
AllowEvent = "allowevent",
|
AllowEvent = "allowevent",
|
||||||
BanEvent = "banevent",
|
BanEvent = "banevent",
|
||||||
|
|||||||
@@ -1,4 +1,14 @@
|
|||||||
import {now, tryCatch, fetchJson, hexToBech32, fromPairs, sum, allPass, nthEq, nth} from "@welshman/lib"
|
import {
|
||||||
|
now,
|
||||||
|
tryCatch,
|
||||||
|
fetchJson,
|
||||||
|
hexToBech32,
|
||||||
|
fromPairs,
|
||||||
|
sum,
|
||||||
|
allPass,
|
||||||
|
nthEq,
|
||||||
|
nth,
|
||||||
|
} from "@welshman/lib"
|
||||||
import {ZAP_RECEIPT, ZAP_REQUEST} from "./Kinds.js"
|
import {ZAP_RECEIPT, ZAP_REQUEST} from "./Kinds.js"
|
||||||
import {getTagValue} from "./Tags.js"
|
import {getTagValue} from "./Tags.js"
|
||||||
import type {Filter} from "./Filters.js"
|
import type {Filter} from "./Filters.js"
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ description: "Use this skill when working with @welshman/util: nostr event types
|
|||||||
|
|
||||||
`@welshman/util` is the foundational layer of the welshman nostr stack, providing types, constants, and helpers for every nostr primitive: events, kinds, tags, filters, addresses, zaps, relays, and Lightning wallet integration. Higher level welshman packages (`@welshman/net`, `@welshman/app`, `@welshman/store`, etc.) depend on the types and utilities defined here.
|
`@welshman/util` is the foundational layer of the welshman nostr stack, providing types, constants, and helpers for every nostr primitive: events, kinds, tags, filters, addresses, zaps, relays, and Lightning wallet integration. Higher level welshman packages (`@welshman/net`, `@welshman/app`, `@welshman/store`, etc.) depend on the types and utilities defined here.
|
||||||
|
|
||||||
> **Moved to `@welshman/domain`:** Profiles, lists, handlers, rooms, and Encryptable (`makeProfile`/`readProfile`/`displayProfile`, `makeList`/`addToList*`, `readHandlers`/`displayHandler`, room helpers, `Encryptable`/`asDecryptedEvent`/`DecryptedEvent`, etc.) now live in `@welshman/domain` as Reader/Builder classes — see the welshman-domain skill.
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
Reference in New Issue
Block a user