Re-work publishing of wrapped events
This commit is contained in:
@@ -28,7 +28,7 @@ import {
|
|||||||
PINS,
|
PINS,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import type {RoomMeta, Profile} from "@welshman/util"
|
import type {RoomMeta, Profile} from "@welshman/util"
|
||||||
import {Nip59, stamp} from "@welshman/signer"
|
import {Nip59, stamp, hash, own} from "@welshman/signer"
|
||||||
import {Router, addMaximalFallbacks} from "@welshman/router"
|
import {Router, addMaximalFallbacks} from "@welshman/router"
|
||||||
import {
|
import {
|
||||||
userRelaySelections,
|
userRelaySelections,
|
||||||
@@ -205,24 +205,19 @@ export const pin = async (tag: string[]) => {
|
|||||||
// NIP 59
|
// NIP 59
|
||||||
|
|
||||||
export type SendWrappedOptions = Omit<ThunkOptions, "event" | "relays"> & {
|
export type SendWrappedOptions = Omit<ThunkOptions, "event" | "relays"> & {
|
||||||
template: EventTemplate
|
event: EventTemplate
|
||||||
pubkeys: string[]
|
recipients: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const sendWrapped = async ({template, pubkeys, ...options}: SendWrappedOptions) => {
|
export const sendWrapped = async ({event, recipients, ...options}: SendWrappedOptions) =>
|
||||||
const nip59 = Nip59.fromSigner(signer.get()!)
|
new MergedThunk(
|
||||||
|
uniq(recipients)
|
||||||
return new MergedThunk(
|
.map(recipient => {
|
||||||
await Promise.all(
|
|
||||||
uniq(pubkeys).map(async recipient => {
|
|
||||||
const event = await nip59.wrap(recipient, stamp(template))
|
|
||||||
const relays = Router.get().PubkeyInbox(recipient).getUrls()
|
const relays = Router.get().PubkeyInbox(recipient).getUrls()
|
||||||
|
|
||||||
return publishThunk({event, relays, ...options})
|
return publishThunk({event, relays, recipient, ...options})
|
||||||
}),
|
})
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// NIP 86
|
// NIP 86
|
||||||
|
|
||||||
|
|||||||
+79
-67
@@ -1,5 +1,6 @@
|
|||||||
import type {Subscriber} from "svelte/store"
|
import type {Subscriber} from "svelte/store"
|
||||||
import {writable, get} from "svelte/store"
|
import {writable, get} from "svelte/store"
|
||||||
|
import type {Override} from '@welshman/lib'
|
||||||
import {
|
import {
|
||||||
append,
|
append,
|
||||||
reject,
|
reject,
|
||||||
@@ -13,7 +14,6 @@ import {
|
|||||||
nth,
|
nth,
|
||||||
without,
|
without,
|
||||||
} from "@welshman/lib"
|
} from "@welshman/lib"
|
||||||
import {stamp, own, hash} from "@welshman/signer"
|
|
||||||
import {
|
import {
|
||||||
TrustedEvent,
|
TrustedEvent,
|
||||||
HashedEvent,
|
HashedEvent,
|
||||||
@@ -26,6 +26,7 @@ import {
|
|||||||
isHashedEvent,
|
isHashedEvent,
|
||||||
isUnwrappedEvent,
|
isUnwrappedEvent,
|
||||||
isSignedEvent,
|
isSignedEvent,
|
||||||
|
WRAPPED_KINDS,
|
||||||
} from "@welshman/util"
|
} from "@welshman/util"
|
||||||
import {
|
import {
|
||||||
publish,
|
publish,
|
||||||
@@ -34,42 +35,46 @@ import {
|
|||||||
PublishOptions,
|
PublishOptions,
|
||||||
PublishResultsByRelay,
|
PublishResultsByRelay,
|
||||||
} from "@welshman/net"
|
} from "@welshman/net"
|
||||||
|
import {ISigner, Nip59, prep} from '@welshman/signer'
|
||||||
import {repository, tracker} from "./core.js"
|
import {repository, tracker} from "./core.js"
|
||||||
import {pubkey, getSession, getSigner} from "./session.js"
|
import {pubkey, signer} from "./session.js"
|
||||||
|
|
||||||
export type ThunkEvent = EventTemplate | StampedEvent | OwnedEvent | TrustedEvent
|
export type ThunkOptions = Override<PublishOptions, {
|
||||||
|
event: EventTemplate
|
||||||
export const prepEvent = (event: ThunkEvent) => {
|
recipient?: string
|
||||||
if (!isStampedEvent(event as StampedEvent)) {
|
|
||||||
event = stamp(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isOwnedEvent(event as OwnedEvent)) {
|
|
||||||
event = own(event as StampedEvent, get(pubkey)!)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isHashedEvent(event as HashedEvent)) {
|
|
||||||
event = hash(event as OwnedEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
return event as TrustedEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ThunkOptions = Omit<PublishOptions, "event"> & {
|
|
||||||
event: ThunkEvent
|
|
||||||
delay?: number
|
delay?: number
|
||||||
}
|
}>
|
||||||
|
|
||||||
export class Thunk {
|
export class Thunk {
|
||||||
_subs: Subscriber<Thunk>[] = []
|
_subs: Subscriber<Thunk>[] = []
|
||||||
|
|
||||||
event: TrustedEvent
|
pubkey: string
|
||||||
|
signer: ISigner
|
||||||
|
event: HashedEvent
|
||||||
results: PublishResultsByRelay = {}
|
results: PublishResultsByRelay = {}
|
||||||
complete = defer<void>()
|
complete = defer<void>()
|
||||||
controller = new AbortController()
|
controller = new AbortController()
|
||||||
|
|
||||||
constructor(readonly options: ThunkOptions) {
|
constructor(readonly options: ThunkOptions) {
|
||||||
this.event = prepEvent(options.event)
|
if (!options.recipient && WRAPPED_KINDS.includes(options.event.kind)) {
|
||||||
|
throw new Error(`Attempted to publish a kind ${options.event.kind} without wrapping it`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const $pubkey = pubkey.get()
|
||||||
|
|
||||||
|
if (!$pubkey) {
|
||||||
|
throw new Error(`Attempted to publish an event without an active pubkey`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const $signer = signer.get()
|
||||||
|
|
||||||
|
if (!$signer) {
|
||||||
|
throw new Error(`Attempted to publish an event without an active signer`)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.pubkey = $pubkey
|
||||||
|
this.signer = $signer
|
||||||
|
this.event = prep(options.event, this.pubkey)
|
||||||
|
|
||||||
for (const relay of options.relays) {
|
for (const relay of options.relays) {
|
||||||
this.results[relay] = {
|
this.results[relay] = {
|
||||||
@@ -126,41 +131,10 @@ export class Thunk {
|
|||||||
this._notify()
|
this._notify()
|
||||||
}
|
}
|
||||||
|
|
||||||
async publish() {
|
async _publish(event: SignedEvent) {
|
||||||
let event = this.event
|
// Copy the signature over since we may have deferred signing
|
||||||
|
ifLet(repository.getEvent(event.id), savedEvent => {
|
||||||
// Handle abort immediately if possible
|
savedEvent.sig = event.sig
|
||||||
if (this.controller.signal.aborted) return
|
|
||||||
|
|
||||||
// If we were given a wrapped event, make sure to publish the wrapper, not the rumor
|
|
||||||
if (isUnwrappedEvent(event)) {
|
|
||||||
event = event.wrap
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the event was already signed, leave it alone. Otherwise, sign it now. This is to
|
|
||||||
// decrease apparent latency in the UI that results from waiting for remote signers
|
|
||||||
if (!isSignedEvent(event)) {
|
|
||||||
const signer = getSigner(getSession(event.pubkey))
|
|
||||||
|
|
||||||
if (!signer) {
|
|
||||||
return this._fail(`No signer found for ${event.pubkey}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
event = await signer.sign(event, {
|
|
||||||
signal: AbortSignal.timeout(15_000),
|
|
||||||
})
|
|
||||||
} catch (e: any) {
|
|
||||||
return this._fail(String(e || "Failed to sign event"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're guaranteed to have a signed event at this point
|
|
||||||
const signedEvent = event as SignedEvent
|
|
||||||
|
|
||||||
// Copy the signature over since we had deferred signing
|
|
||||||
ifLet(repository.getEvent(signedEvent.id), savedEvent => {
|
|
||||||
savedEvent.sig = signedEvent.sig
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Wait if the thunk is to be delayed
|
// Wait if the thunk is to be delayed
|
||||||
@@ -176,9 +150,9 @@ export class Thunk {
|
|||||||
// Send it off
|
// Send it off
|
||||||
await publish({
|
await publish({
|
||||||
...this.options,
|
...this.options,
|
||||||
event: signedEvent,
|
event,
|
||||||
onSuccess: (result: PublishResult) => {
|
onSuccess: (result: PublishResult) => {
|
||||||
tracker.track(signedEvent.id, result.relay)
|
tracker.track(event.id, result.relay)
|
||||||
this.options.onSuccess?.(result)
|
this.options.onSuccess?.(result)
|
||||||
this.results[result.relay] = result
|
this.results[result.relay] = result
|
||||||
this._notify()
|
this._notify()
|
||||||
@@ -197,9 +171,51 @@ export class Thunk {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Notify the caller that we're done
|
||||||
this.complete.resolve()
|
this.complete.resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async publish() {
|
||||||
|
// Handle abort immediately if possible
|
||||||
|
if (this.controller.signal.aborted) return
|
||||||
|
|
||||||
|
// If we were given an event with wraps, reject it (this used to be allowed)
|
||||||
|
if (isUnwrappedEvent(this.event)) {
|
||||||
|
throw new Error("Attempted to publish an unwrapped event")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're sending it privately, wrap the event using nip 59
|
||||||
|
if (this.options.recipient) {
|
||||||
|
const nip59 = Nip59.fromSigner(this.signer)
|
||||||
|
const event = await nip59.wrap(this.options.recipient, this.event)
|
||||||
|
|
||||||
|
return this._publish(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the event has been signed, we're good to go
|
||||||
|
if (isSignedEvent(this.event)) {
|
||||||
|
return this._publish(this.event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow for lazily signing events in order to decrease apparent latency in the UI
|
||||||
|
// that results from waiting for remote signers
|
||||||
|
try {
|
||||||
|
return this._publish(
|
||||||
|
await this.signer.sign(this.event, {
|
||||||
|
signal: AbortSignal.timeout(15_000),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} catch (e: any) {
|
||||||
|
return this._fail(String(e || "Failed to sign event"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enqueue() {
|
||||||
|
thunkQueue.push(this)
|
||||||
|
repository.publish(this.event)
|
||||||
|
thunks.update($thunks => append(this, $thunks))
|
||||||
|
}
|
||||||
|
|
||||||
subscribe(subscriber: Subscriber<Thunk>) {
|
subscribe(subscriber: Subscriber<Thunk>) {
|
||||||
this._subs.push(subscriber)
|
this._subs.push(subscriber)
|
||||||
|
|
||||||
@@ -365,11 +381,7 @@ export function* flattenThunks(thunks: AbstractThunk[]): Iterable<Thunk> {
|
|||||||
export const publishThunk = (options: ThunkOptions) => {
|
export const publishThunk = (options: ThunkOptions) => {
|
||||||
const thunk = new Thunk(options)
|
const thunk = new Thunk(options)
|
||||||
|
|
||||||
thunkQueue.push(thunk)
|
thunk.enqueue()
|
||||||
|
|
||||||
repository.publish(thunk.event)
|
|
||||||
|
|
||||||
thunks.update($thunks => append(thunk, $thunks))
|
|
||||||
|
|
||||||
return thunk
|
return thunk
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -290,11 +290,11 @@ describe("Repository", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("should handle wrapped events", () => {
|
it("should handle wrapped events", () => {
|
||||||
const event: TrustedEvent = createEvent(1, {wrap: createEvent(1)})
|
const event: TrustedEvent = createEvent(1, {wraps: [createEvent(1)]})
|
||||||
|
|
||||||
repo.publish(event)
|
repo.publish(event)
|
||||||
|
|
||||||
expect(repo.eventsByWrap.get(event.wrap!.id)).toEqual(event)
|
expect(repo.eventsByWrap.get(event.wraps!.[0]!.id)).toEqual(event)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ describe("Repository", () => {
|
|||||||
|
|
||||||
it("should remove wrapped events", () => {
|
it("should remove wrapped events", () => {
|
||||||
const wrapped = createEvent(1)
|
const wrapped = createEvent(1)
|
||||||
const event = createEvent(1, {wrap: wrapped})
|
const event = createEvent(1, {wraps: [wrapped]})
|
||||||
|
|
||||||
repo.publish(event)
|
repo.publish(event)
|
||||||
repo.removeEvent(event.id)
|
repo.removeEvent(event.id)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export type RepositoryUpdate = {
|
|||||||
removed: Set<string>
|
removed: Set<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Repository<E extends HashedEvent = TrustedEvent> extends Emitter {
|
export class Repository<E extends TrustedEvent = TrustedEvent> extends Emitter {
|
||||||
eventsById = new Map<string, E>()
|
eventsById = new Map<string, E>()
|
||||||
eventsByWrap = new Map<string, E>()
|
eventsByWrap = new Map<string, E>()
|
||||||
eventsByAddress = new Map<string, E>()
|
eventsByAddress = new Map<string, E>()
|
||||||
@@ -133,8 +133,8 @@ export class Repository<E extends HashedEvent = TrustedEvent> extends Emitter {
|
|||||||
if (event) {
|
if (event) {
|
||||||
this.eventsById.delete(event.id)
|
this.eventsById.delete(event.id)
|
||||||
|
|
||||||
if (isUnwrappedEvent(event)) {
|
for (const wrap of event.wraps || []) {
|
||||||
this.eventsByWrap.delete(event.wrap.id)
|
this.eventsByWrap.delete(wrap.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.eventsByAddress.delete(getAddress(event))
|
this.eventsByAddress.delete(getAddress(event))
|
||||||
@@ -235,8 +235,8 @@ export class Repository<E extends HashedEvent = TrustedEvent> extends Emitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save wrapper index
|
// Save wrapper index
|
||||||
if (isUnwrappedEvent(event)) {
|
for (const wrap of event.wraps || []) {
|
||||||
this.eventsByWrap.set(event.wrap.id, event)
|
this.eventsByWrap.set(wrap.id, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update our timestamp and author indexes
|
// Update our timestamp and author indexes
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import {UnwrappedEvent, SignedEvent, HashedEvent, StampedEvent, WRAP, SEAL} from "@welshman/util"
|
import {isHashedEvent, SignedEvent, HashedEvent, StampedEvent, WRAP, SEAL} from "@welshman/util"
|
||||||
import {own, hash, decrypt, ISigner} from "./util.js"
|
import {prep, hash, decrypt, ISigner} from "./util.js"
|
||||||
import {Nip01Signer} from "./signers/nip01.js"
|
import {Nip01Signer} from "./signers/nip01.js"
|
||||||
|
|
||||||
export const seen = new Map<string, UnwrappedEvent | Error>()
|
export const seen = new Map<string, HashedEvent | Error>()
|
||||||
|
|
||||||
export const now = (drift = 0) =>
|
export const now = (drift = 0) =>
|
||||||
Math.round(Date.now() / 1000 - Math.random() * Math.pow(10, drift))
|
Math.round(Date.now() / 1000 - Math.random() * Math.pow(10, drift))
|
||||||
|
|
||||||
export const getRumor = async (signer: ISigner, template: StampedEvent) =>
|
|
||||||
hash(own(template, await signer.getPubkey()))
|
|
||||||
|
|
||||||
export const getSeal = async (signer: ISigner, pubkey: string, rumor: HashedEvent) =>
|
export const getSeal = async (signer: ISigner, pubkey: string, rumor: HashedEvent) =>
|
||||||
signer.sign(
|
signer.sign(
|
||||||
hash({
|
hash({
|
||||||
@@ -44,11 +41,12 @@ export const wrap = async (
|
|||||||
template: StampedEvent,
|
template: StampedEvent,
|
||||||
tags: string[][] = [],
|
tags: string[][] = [],
|
||||||
) => {
|
) => {
|
||||||
const rumor = await getRumor(signer, template)
|
const author = await signer.getPubkey()
|
||||||
|
const rumor = await prep(template, author)
|
||||||
const seal = await getSeal(signer, pubkey, rumor)
|
const seal = await getSeal(signer, pubkey, rumor)
|
||||||
const wrap = await getWrap(wrapper, pubkey, seal, tags)
|
const wrap = await getWrap(wrapper, pubkey, seal, tags)
|
||||||
|
|
||||||
return Object.assign(rumor, {wrap}) as UnwrappedEvent
|
return wrap
|
||||||
}
|
}
|
||||||
|
|
||||||
export const unwrap = async (signer: ISigner, wrap: SignedEvent) => {
|
export const unwrap = async (signer: ISigner, wrap: SignedEvent) => {
|
||||||
@@ -68,10 +66,11 @@ export const unwrap = async (signer: ISigner, wrap: SignedEvent) => {
|
|||||||
const rumor = JSON.parse(await decrypt(signer, seal.pubkey, seal.content))
|
const rumor = JSON.parse(await decrypt(signer, seal.pubkey, seal.content))
|
||||||
|
|
||||||
if (seal.pubkey !== rumor.pubkey) throw new Error("Seal pubkey does not match rumor pubkey")
|
if (seal.pubkey !== rumor.pubkey) throw new Error("Seal pubkey does not match rumor pubkey")
|
||||||
|
if (!isHashedEvent(rumor)) throw new Error("Unwrapped object was not a hashed event")
|
||||||
|
|
||||||
seen.set(wrap.id, rumor)
|
seen.set(wrap.id, rumor)
|
||||||
|
|
||||||
return Object.assign(rumor, {wrap}) as UnwrappedEvent
|
return rumor
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
seen.set(wrap.id, error as Error)
|
seen.set(wrap.id, error as Error)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import * as nt04 from "nostr-tools/nip04"
|
|||||||
import * as nt44 from "nostr-tools/nip44"
|
import * as nt44 from "nostr-tools/nip44"
|
||||||
import {generateSecretKey, getPublicKey, getEventHash} from "nostr-tools/pure"
|
import {generateSecretKey, getPublicKey, getEventHash} from "nostr-tools/pure"
|
||||||
import {Emitter, cached, now} from "@welshman/lib"
|
import {Emitter, cached, now} from "@welshman/lib"
|
||||||
import {SignedEvent, HashedEvent, EventTemplate, StampedEvent, OwnedEvent} from "@welshman/util"
|
import {SignedEvent, HashedEvent, EventTemplate, StampedEvent, OwnedEvent, isStampedEvent, isOwnedEvent, isHashedEvent} from "@welshman/util"
|
||||||
|
|
||||||
export const makeSecret = () => bytesToHex(generateSecretKey())
|
export const makeSecret = () => bytesToHex(generateSecretKey())
|
||||||
|
|
||||||
@@ -21,6 +21,22 @@ export const own = (event: StampedEvent, pubkey: string) => ({...event, pubkey})
|
|||||||
|
|
||||||
export const hash = (event: OwnedEvent) => ({...event, id: getHash(event)})
|
export const hash = (event: OwnedEvent) => ({...event, id: getHash(event)})
|
||||||
|
|
||||||
|
export const prep = (event: EventTemplate, pubkey: string, created_at = now()) => {
|
||||||
|
if (!isStampedEvent(event as StampedEvent)) {
|
||||||
|
event = stamp(event, created_at)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOwnedEvent(event as OwnedEvent)) {
|
||||||
|
event = own(event as StampedEvent, pubkey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isHashedEvent(event as HashedEvent)) {
|
||||||
|
event = hash(event as OwnedEvent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return event as HashedEvent
|
||||||
|
}
|
||||||
|
|
||||||
export const sign = (event: HashedEvent, secret: string) => ({...event, sig: getSig(event, secret)})
|
export const sign = (event: HashedEvent, secret: string) => ({...event, sig: getSig(event, secret)})
|
||||||
|
|
||||||
export const nip04 = {
|
export const nip04 = {
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ describe("Events", () => {
|
|||||||
it("should validate TrustedEvent", () => {
|
it("should validate TrustedEvent", () => {
|
||||||
const unwrapped = {
|
const unwrapped = {
|
||||||
...createHashedEvent(),
|
...createHashedEvent(),
|
||||||
wrap: createSignedEvent(),
|
wraps: [createSignedEvent()],
|
||||||
}
|
}
|
||||||
expect(Events.isTrustedEvent(createHashedEvent())).toBe(false)
|
expect(Events.isTrustedEvent(createHashedEvent())).toBe(false)
|
||||||
expect(Events.isTrustedEvent(createSignedEvent())).toBe(true)
|
expect(Events.isTrustedEvent(createSignedEvent())).toBe(true)
|
||||||
@@ -115,7 +115,7 @@ describe("Events", () => {
|
|||||||
it("should validate UnwrappedEvent", () => {
|
it("should validate UnwrappedEvent", () => {
|
||||||
const unwrapped = {
|
const unwrapped = {
|
||||||
...createHashedEvent(),
|
...createHashedEvent(),
|
||||||
wrap: createSignedEvent(),
|
wraps: [createSignedEvent()],
|
||||||
}
|
}
|
||||||
expect(Events.isUnwrappedEvent(unwrapped)).toBe(true)
|
expect(Events.isUnwrappedEvent(unwrapped)).toBe(true)
|
||||||
expect(Events.isUnwrappedEvent(createHashedEvent())).toBe(false)
|
expect(Events.isUnwrappedEvent(createHashedEvent())).toBe(false)
|
||||||
@@ -152,10 +152,10 @@ describe("Events", () => {
|
|||||||
const trustedEvent = {
|
const trustedEvent = {
|
||||||
...createHashedEvent(),
|
...createHashedEvent(),
|
||||||
sig: sig,
|
sig: sig,
|
||||||
wrap: createSignedEvent(),
|
wraps: [createSignedEvent()],
|
||||||
}
|
}
|
||||||
const result = Events.asSignedEvent(trustedEvent)
|
const result = Events.asSignedEvent(trustedEvent)
|
||||||
expect(result).not.toHaveProperty("wrap")
|
expect(result).not.toHaveProperty("wraps")
|
||||||
expect(result).toHaveProperty("sig")
|
expect(result).toHaveProperty("sig")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -163,10 +163,10 @@ describe("Events", () => {
|
|||||||
const trustedEvent = {
|
const trustedEvent = {
|
||||||
...createHashedEvent(),
|
...createHashedEvent(),
|
||||||
sig: sig,
|
sig: sig,
|
||||||
wrap: createSignedEvent(),
|
wraps: [createSignedEvent()],
|
||||||
}
|
}
|
||||||
const result = Events.asUnwrappedEvent(trustedEvent)
|
const result = Events.asUnwrappedEvent(trustedEvent)
|
||||||
expect(result).toHaveProperty("wrap")
|
expect(result).toHaveProperty("wraps")
|
||||||
expect(result).not.toHaveProperty("sig")
|
expect(result).not.toHaveProperty("sig")
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -174,11 +174,11 @@ describe("Events", () => {
|
|||||||
const trustedEvent = {
|
const trustedEvent = {
|
||||||
...createHashedEvent(),
|
...createHashedEvent(),
|
||||||
sig: sig,
|
sig: sig,
|
||||||
wrap: createSignedEvent(),
|
wraps: [createSignedEvent()],
|
||||||
}
|
}
|
||||||
const result = Events.asTrustedEvent(trustedEvent)
|
const result = Events.asTrustedEvent(trustedEvent)
|
||||||
expect(result).toHaveProperty("sig")
|
expect(result).toHaveProperty("sig")
|
||||||
expect(result).toHaveProperty("wrap")
|
expect(result).toHaveProperty("wraps")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -185,7 +185,7 @@ describe("Filters", () => {
|
|||||||
|
|
||||||
it("should handle wrapped events", () => {
|
it("should handle wrapped events", () => {
|
||||||
const event = createEvent({
|
const event = createEvent({
|
||||||
wrap: createEvent(),
|
wraps: [createEvent()],
|
||||||
})
|
})
|
||||||
const result = getReplyFilters([event])
|
const result = getReplyFilters([event])
|
||||||
expect((result[0] as any)["#e"]).toHaveLength(2)
|
expect((result[0] as any)["#e"]).toHaveLength(2)
|
||||||
|
|||||||
@@ -41,12 +41,12 @@ export type SignedEvent = HashedEvent & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type UnwrappedEvent = HashedEvent & {
|
export type UnwrappedEvent = HashedEvent & {
|
||||||
wrap: SignedEvent
|
wraps: SignedEvent[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TrustedEvent = HashedEvent & {
|
export type TrustedEvent = HashedEvent & {
|
||||||
sig?: string
|
sig?: string
|
||||||
wrap?: SignedEvent
|
wraps?: SignedEvent[]
|
||||||
[verifiedSymbol]?: boolean
|
[verifiedSymbol]?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +111,7 @@ export const isSignedEvent = (e: TrustedEvent): e is SignedEvent =>
|
|||||||
Boolean(isHashedEvent(e) && typeof e.sig === "string" && e.sig.length > 0)
|
Boolean(isHashedEvent(e) && typeof e.sig === "string" && e.sig.length > 0)
|
||||||
|
|
||||||
export const isUnwrappedEvent = (e: TrustedEvent): e is UnwrappedEvent =>
|
export const isUnwrappedEvent = (e: TrustedEvent): e is UnwrappedEvent =>
|
||||||
Boolean(isHashedEvent(e) && e.wrap && isSignedEvent(e.wrap))
|
Boolean(isHashedEvent(e) && e.wraps?.every(isSignedEvent))
|
||||||
|
|
||||||
export const isTrustedEvent = (e: TrustedEvent): e is TrustedEvent =>
|
export const isTrustedEvent = (e: TrustedEvent): e is TrustedEvent =>
|
||||||
isSignedEvent(e) || isUnwrappedEvent(e)
|
isSignedEvent(e) || isUnwrappedEvent(e)
|
||||||
@@ -134,10 +134,10 @@ export const asSignedEvent = (e: SignedEvent): SignedEvent =>
|
|||||||
pick(["kind", "tags", "content", "created_at", "pubkey", "id", "sig"], e)
|
pick(["kind", "tags", "content", "created_at", "pubkey", "id", "sig"], e)
|
||||||
|
|
||||||
export const asUnwrappedEvent = (e: UnwrappedEvent): UnwrappedEvent =>
|
export const asUnwrappedEvent = (e: UnwrappedEvent): UnwrappedEvent =>
|
||||||
pick(["kind", "tags", "content", "created_at", "pubkey", "id", "wrap"], e)
|
pick(["kind", "tags", "content", "created_at", "pubkey", "id", "wraps"], e)
|
||||||
|
|
||||||
export const asTrustedEvent = (e: TrustedEvent): TrustedEvent =>
|
export const asTrustedEvent = (e: TrustedEvent): TrustedEvent =>
|
||||||
pick(["kind", "tags", "content", "created_at", "pubkey", "id", "sig", "wrap"], e)
|
pick(["kind", "tags", "content", "created_at", "pubkey", "id", "sig", "wraps"], e)
|
||||||
|
|
||||||
// Utilities for working with events
|
// Utilities for working with events
|
||||||
|
|
||||||
|
|||||||
@@ -179,9 +179,7 @@ export const getReplyFilters = (events: TrustedEvent[], filter: Filter = {}) =>
|
|||||||
a.push(getAddress(event))
|
a.push(getAddress(event))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.wrap) {
|
event.wraps?.forEach(wrap => e.push(wrap.id))
|
||||||
e.push(event.wrap.id)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const filters = []
|
const filters = []
|
||||||
|
|||||||
@@ -197,3 +197,8 @@ export const FOLLOW_PACK = 39089
|
|||||||
export const DEPRECATED_RELAY_RECOMMENDATION = 2
|
export const DEPRECATED_RELAY_RECOMMENDATION = 2
|
||||||
export const DEPRECATED_DIRECT_MESSAGE = 4
|
export const DEPRECATED_DIRECT_MESSAGE = 4
|
||||||
export const DEPRECATED_NAMED_GENERIC = 30001
|
export const DEPRECATED_NAMED_GENERIC = 30001
|
||||||
|
|
||||||
|
export const WRAPPED_KINDS = [
|
||||||
|
DIRECT_MESSAGE,
|
||||||
|
DIRECT_MESSAGE_FILE,
|
||||||
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user